servo/components/layout/webrender_helpers.rs
Martin Robinson be62ad7117 Integrate stacking contexts into the display list
Integrate stacking contexts into the display list by adding two new
entry types, PushStackingContext and PopStackingContext. This allows us
to eliminate the ugly offsets map that DisplayList used to contain
and seems to speed up display list construction. With this approach
we are able to also completely prune pseudo-stacking contexts from the
final display list and remove their (minimal) overhead from display
list traversal Traversing the display list is also a bit simpler now.

Additionally, this will allow easier editing of the DisplayList to
properly support scrolling roots. The push/pop entries can be
duplicated to clone complex StackingContext trees between layers.
2016-10-13 09:50:45 +02:00

592 lines
27 KiB
Rust

/* 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/. */
// TODO(gw): This contains helper traits and implementations for converting Servo display lists
// into WebRender display lists. In the future, this step should be completely removed.
// This might be achieved by sharing types between WR and Servo display lists, or
// completely converting layout to directly generate WebRender display lists, for example.
use app_units::Au;
use azure::azure_hl::Color;
use euclid::{Point2D, Rect, Size2D};
use gfx::display_list::{BorderRadii, BoxShadowClipMode, ClippingRegion};
use gfx::display_list::{DisplayItem, DisplayList, DisplayListTraversal};
use gfx::display_list::{GradientStop, StackingContext, StackingContextType};
use gfx_traits::{FragmentType, ScrollPolicy, StackingContextId};
use style::computed_values::{image_rendering, mix_blend_mode};
use style::computed_values::filter::{self, Filter};
use style::values::computed::BorderStyle;
use webrender_traits::{self, AuxiliaryListsBuilder, DisplayListId, PipelineId};
trait WebRenderStackingContextConverter {
fn convert_to_webrender<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
api: &mut webrender_traits::RenderApi,
pipeline_id: webrender_traits::PipelineId,
epoch: webrender_traits::Epoch,
scroll_layer_id: Option<webrender_traits::ScrollLayerId>,
scroll_policy: ScrollPolicy,
frame_builder: &mut WebRenderFrameBuilder)
-> webrender_traits::StackingContextId;
fn convert_children_to_webrender<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
api: &mut webrender_traits::RenderApi,
pipeline_id: webrender_traits::PipelineId,
epoch: webrender_traits::Epoch,
scroll_layer_id: Option<webrender_traits::ScrollLayerId>,
scroll_policy: ScrollPolicy,
builder: &mut webrender_traits::DisplayListBuilder,
frame_builder: &mut WebRenderFrameBuilder,
force_positioned_stacking_level: bool);
}
pub trait WebRenderDisplayListConverter {
fn convert_to_webrender(&self,
api: &mut webrender_traits::RenderApi,
pipeline_id: webrender_traits::PipelineId,
epoch: webrender_traits::Epoch,
scroll_layer_id: Option<webrender_traits::ScrollLayerId>,
frame_builder: &mut WebRenderFrameBuilder)
-> webrender_traits::StackingContextId;
}
trait WebRenderDisplayItemConverter {
fn convert_to_webrender(&self,
builder: &mut webrender_traits::DisplayListBuilder,
frame_builder: &mut WebRenderFrameBuilder);
}
trait ToBorderStyle {
fn to_border_style(&self) -> webrender_traits::BorderStyle;
}
impl ToBorderStyle for BorderStyle {
fn to_border_style(&self) -> webrender_traits::BorderStyle {
match *self {
BorderStyle::none => webrender_traits::BorderStyle::None,
BorderStyle::solid => webrender_traits::BorderStyle::Solid,
BorderStyle::double => webrender_traits::BorderStyle::Double,
BorderStyle::dotted => webrender_traits::BorderStyle::Dotted,
BorderStyle::dashed => webrender_traits::BorderStyle::Dashed,
BorderStyle::hidden => webrender_traits::BorderStyle::Hidden,
BorderStyle::groove => webrender_traits::BorderStyle::Groove,
BorderStyle::ridge => webrender_traits::BorderStyle::Ridge,
BorderStyle::inset => webrender_traits::BorderStyle::Inset,
BorderStyle::outset => webrender_traits::BorderStyle::Outset,
}
}
}
trait ToBoxShadowClipMode {
fn to_clip_mode(&self) -> webrender_traits::BoxShadowClipMode;
}
impl ToBoxShadowClipMode for BoxShadowClipMode {
fn to_clip_mode(&self) -> webrender_traits::BoxShadowClipMode {
match *self {
BoxShadowClipMode::None => webrender_traits::BoxShadowClipMode::None,
BoxShadowClipMode::Inset => webrender_traits::BoxShadowClipMode::Inset,
BoxShadowClipMode::Outset => webrender_traits::BoxShadowClipMode::Outset,
}
}
}
trait ToSizeF {
fn to_sizef(&self) -> Size2D<f32>;
}
trait ToPointF {
fn to_pointf(&self) -> Point2D<f32>;
}
impl ToPointF for Point2D<Au> {
fn to_pointf(&self) -> Point2D<f32> {
Point2D::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}
impl ToSizeF for Size2D<Au> {
fn to_sizef(&self) -> Size2D<f32> {
Size2D::new(self.width.to_f32_px(), self.height.to_f32_px())
}
}
trait ToRectF {
fn to_rectf(&self) -> Rect<f32>;
}
impl ToRectF for Rect<Au> {
fn to_rectf(&self) -> Rect<f32> {
let x = self.origin.x.to_f32_px();
let y = self.origin.y.to_f32_px();
let w = self.size.width.to_f32_px();
let h = self.size.height.to_f32_px();
Rect::new(Point2D::new(x, y), Size2D::new(w, h))
}
}
trait ToColorF {
fn to_colorf(&self) -> webrender_traits::ColorF;
}
impl ToColorF for Color {
fn to_colorf(&self) -> webrender_traits::ColorF {
webrender_traits::ColorF::new(self.r, self.g, self.b, self.a)
}
}
trait ToGradientStop {
fn to_gradient_stop(&self) -> webrender_traits::GradientStop;
}
impl ToGradientStop for GradientStop {
fn to_gradient_stop(&self) -> webrender_traits::GradientStop {
webrender_traits::GradientStop {
offset: self.offset,
color: self.color.to_colorf(),
}
}
}
trait ToClipRegion {
fn to_clip_region(&self, frame_builder: &mut WebRenderFrameBuilder)
-> webrender_traits::ClipRegion;
}
impl ToClipRegion for ClippingRegion {
fn to_clip_region(&self, frame_builder: &mut WebRenderFrameBuilder)
-> webrender_traits::ClipRegion {
webrender_traits::ClipRegion::new(&self.main.to_rectf(),
self.complex.iter().map(|complex_clipping_region| {
webrender_traits::ComplexClipRegion::new(
complex_clipping_region.rect.to_rectf(),
complex_clipping_region.radii.to_border_radius(),
)
}).collect(),
&mut frame_builder.auxiliary_lists_builder)
}
}
trait ToBorderRadius {
fn to_border_radius(&self) -> webrender_traits::BorderRadius;
}
impl ToBorderRadius for BorderRadii<Au> {
fn to_border_radius(&self) -> webrender_traits::BorderRadius {
webrender_traits::BorderRadius {
top_left: self.top_left.to_sizef(),
top_right: self.top_right.to_sizef(),
bottom_left: self.bottom_left.to_sizef(),
bottom_right: self.bottom_right.to_sizef(),
}
}
}
trait ToBlendMode {
fn to_blend_mode(&self) -> webrender_traits::MixBlendMode;
}
impl ToBlendMode for mix_blend_mode::T {
fn to_blend_mode(&self) -> webrender_traits::MixBlendMode {
match *self {
mix_blend_mode::T::normal => webrender_traits::MixBlendMode::Normal,
mix_blend_mode::T::multiply => webrender_traits::MixBlendMode::Multiply,
mix_blend_mode::T::screen => webrender_traits::MixBlendMode::Screen,
mix_blend_mode::T::overlay => webrender_traits::MixBlendMode::Overlay,
mix_blend_mode::T::darken => webrender_traits::MixBlendMode::Darken,
mix_blend_mode::T::lighten => webrender_traits::MixBlendMode::Lighten,
mix_blend_mode::T::color_dodge => webrender_traits::MixBlendMode::ColorDodge,
mix_blend_mode::T::color_burn => webrender_traits::MixBlendMode::ColorBurn,
mix_blend_mode::T::hard_light => webrender_traits::MixBlendMode::HardLight,
mix_blend_mode::T::soft_light => webrender_traits::MixBlendMode::SoftLight,
mix_blend_mode::T::difference => webrender_traits::MixBlendMode::Difference,
mix_blend_mode::T::exclusion => webrender_traits::MixBlendMode::Exclusion,
mix_blend_mode::T::hue => webrender_traits::MixBlendMode::Hue,
mix_blend_mode::T::saturation => webrender_traits::MixBlendMode::Saturation,
mix_blend_mode::T::color => webrender_traits::MixBlendMode::Color,
mix_blend_mode::T::luminosity => webrender_traits::MixBlendMode::Luminosity,
}
}
}
trait ToImageRendering {
fn to_image_rendering(&self) -> webrender_traits::ImageRendering;
}
impl ToImageRendering for image_rendering::T {
fn to_image_rendering(&self) -> webrender_traits::ImageRendering {
match *self {
image_rendering::T::crispedges => webrender_traits::ImageRendering::CrispEdges,
image_rendering::T::auto => webrender_traits::ImageRendering::Auto,
image_rendering::T::pixelated => webrender_traits::ImageRendering::Pixelated,
}
}
}
trait ToFilterOps {
fn to_filter_ops(&self) -> Vec<webrender_traits::FilterOp>;
}
impl ToFilterOps for filter::T {
fn to_filter_ops(&self) -> Vec<webrender_traits::FilterOp> {
let mut result = Vec::with_capacity(self.filters.len());
for filter in self.filters.iter() {
match *filter {
Filter::Blur(radius) => result.push(webrender_traits::FilterOp::Blur(radius)),
Filter::Brightness(amount) => result.push(webrender_traits::FilterOp::Brightness(amount)),
Filter::Contrast(amount) => result.push(webrender_traits::FilterOp::Contrast(amount)),
Filter::Grayscale(amount) => result.push(webrender_traits::FilterOp::Grayscale(amount)),
Filter::HueRotate(angle) => result.push(webrender_traits::FilterOp::HueRotate(angle.0)),
Filter::Invert(amount) => result.push(webrender_traits::FilterOp::Invert(amount)),
Filter::Opacity(amount) => result.push(webrender_traits::FilterOp::Opacity(amount)),
Filter::Saturate(amount) => result.push(webrender_traits::FilterOp::Saturate(amount)),
Filter::Sepia(amount) => result.push(webrender_traits::FilterOp::Sepia(amount)),
}
}
result
}
}
impl WebRenderStackingContextConverter for StackingContext {
fn convert_children_to_webrender<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
api: &mut webrender_traits::RenderApi,
pipeline_id: webrender_traits::PipelineId,
epoch: webrender_traits::Epoch,
scroll_layer_id: Option<webrender_traits::ScrollLayerId>,
scroll_policy: ScrollPolicy,
builder: &mut webrender_traits::DisplayListBuilder,
frame_builder: &mut WebRenderFrameBuilder,
_force_positioned_stacking_level: bool) {
while let Some(item) = traversal.next() {
match item {
&DisplayItem::PushStackingContextClass(ref stacking_context_item) => {
let stacking_context = &stacking_context_item.stacking_context;
debug_assert!(stacking_context.context_type == StackingContextType::Real);
let scroll_layer_id_for_children = if self.scrolls_overflow_area {
scroll_layer_id
} else {
None
};
let stacking_context_id =
stacking_context.convert_to_webrender(traversal,
api,
pipeline_id,
epoch,
scroll_layer_id_for_children,
scroll_policy,
frame_builder);
builder.push_stacking_context(stacking_context_id);
}
&DisplayItem::PopStackingContextClass(_) => return,
_ => item.convert_to_webrender(builder, frame_builder),
}
}
}
fn convert_to_webrender<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
api: &mut webrender_traits::RenderApi,
pipeline_id: webrender_traits::PipelineId,
epoch: webrender_traits::Epoch,
mut scroll_layer_id: Option<webrender_traits::ScrollLayerId>,
mut scroll_policy: ScrollPolicy,
frame_builder: &mut WebRenderFrameBuilder)
-> webrender_traits::StackingContextId {
if let Some(ref layer_info) = self.layer_info {
scroll_policy = layer_info.scroll_policy
}
let webrender_scroll_policy = match scroll_policy {
ScrollPolicy::Scrollable => webrender_traits::ScrollPolicy::Scrollable,
ScrollPolicy::FixedPosition => webrender_traits::ScrollPolicy::Fixed,
};
let webrender_stacking_context_id = self.id.convert_to_webrender();
let mut sc =
webrender_traits::StackingContext::new(webrender_stacking_context_id,
scroll_layer_id,
webrender_scroll_policy,
self.bounds.to_rectf(),
self.overflow.to_rectf(),
self.z_index,
&self.transform,
&self.perspective,
self.establishes_3d_context,
self.blend_mode.to_blend_mode(),
self.filters.to_filter_ops(),
&mut frame_builder.auxiliary_lists_builder);
let mut builder = webrender_traits::DisplayListBuilder::new();
if self.scrolls_overflow_area {
scroll_layer_id = Some(frame_builder.next_scroll_layer_id());
}
self.convert_children_to_webrender(traversal,
api,
pipeline_id,
epoch,
scroll_layer_id,
scroll_policy,
&mut builder,
frame_builder,
false);
frame_builder.add_display_list(api, builder.finalize(), &mut sc);
frame_builder.add_stacking_context(api, pipeline_id, sc)
}
}
impl WebRenderDisplayListConverter for DisplayList {
fn convert_to_webrender(&self,
api: &mut webrender_traits::RenderApi,
pipeline_id: webrender_traits::PipelineId,
epoch: webrender_traits::Epoch,
scroll_layer_id: Option<webrender_traits::ScrollLayerId>,
frame_builder: &mut WebRenderFrameBuilder)
-> webrender_traits::StackingContextId {
let mut traversal = DisplayListTraversal::new(self);
let item = traversal.next();
match item {
Some(&DisplayItem::PushStackingContextClass(ref stacking_context_item)) => {
let stacking_context = &stacking_context_item.stacking_context;
stacking_context.convert_to_webrender(&mut traversal,
api,
pipeline_id,
epoch,
scroll_layer_id,
ScrollPolicy::Scrollable,
frame_builder)
}
_ => unreachable!("DisplayList did not start with StackingContext."),
}
}
}
impl WebRenderDisplayItemConverter for DisplayItem {
fn convert_to_webrender(&self,
builder: &mut webrender_traits::DisplayListBuilder,
frame_builder: &mut WebRenderFrameBuilder) {
match *self {
DisplayItem::SolidColorClass(ref item) => {
let color = item.color.to_colorf();
if color.a > 0.0 {
builder.push_rect(item.base.bounds.to_rectf(),
item.base.clip.to_clip_region(frame_builder),
color);
}
}
DisplayItem::TextClass(ref item) => {
let mut origin = item.baseline_origin.clone();
let mut glyphs = vec!();
for slice in item.text_run.natural_word_slices_in_visual_order(&item.range) {
for glyph in slice.glyphs.iter_glyphs_for_byte_range(&slice.range) {
let glyph_advance = if glyph.char_is_space() {
glyph.advance() + item.text_run.extra_word_spacing
} else {
glyph.advance()
};
if !slice.glyphs.is_whitespace() {
let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
let glyph = webrender_traits::GlyphInstance {
index: glyph.id(),
x: (origin.x + glyph_offset.x).to_f32_px(),
y: (origin.y + glyph_offset.y).to_f32_px(),
};
glyphs.push(glyph);
}
origin.x = origin.x + glyph_advance;
};
}
if glyphs.len() > 0 {
builder.push_text(item.base.bounds.to_rectf(),
item.base.clip.to_clip_region(frame_builder),
glyphs,
item.text_run.font_key.expect("Font not added to webrender!"),
item.text_color.to_colorf(),
item.text_run.actual_pt_size,
item.blur_radius,
&mut frame_builder.auxiliary_lists_builder);
}
}
DisplayItem::ImageClass(ref item) => {
if let Some(id) = item.webrender_image.key {
if item.stretch_size.width > Au(0) &&
item.stretch_size.height > Au(0) {
builder.push_image(item.base.bounds.to_rectf(),
item.base.clip.to_clip_region(frame_builder),
item.stretch_size.to_sizef(),
item.tile_spacing.to_sizef(),
item.image_rendering.to_image_rendering(),
id);
}
}
}
DisplayItem::WebGLClass(ref item) => {
builder.push_webgl_canvas(item.base.bounds.to_rectf(),
item.base.clip.to_clip_region(frame_builder),
item.context_id);
}
DisplayItem::BorderClass(ref item) => {
let rect = item.base.bounds.to_rectf();
let left = webrender_traits::BorderSide {
width: item.border_widths.left.to_f32_px(),
color: item.color.left.to_colorf(),
style: item.style.left.to_border_style(),
};
let top = webrender_traits::BorderSide {
width: item.border_widths.top.to_f32_px(),
color: item.color.top.to_colorf(),
style: item.style.top.to_border_style(),
};
let right = webrender_traits::BorderSide {
width: item.border_widths.right.to_f32_px(),
color: item.color.right.to_colorf(),
style: item.style.right.to_border_style(),
};
let bottom = webrender_traits::BorderSide {
width: item.border_widths.bottom.to_f32_px(),
color: item.color.bottom.to_colorf(),
style: item.style.bottom.to_border_style(),
};
let radius = item.radius.to_border_radius();
builder.push_border(rect,
item.base.clip.to_clip_region(frame_builder),
left,
top,
right,
bottom,
radius);
}
DisplayItem::GradientClass(ref item) => {
let rect = item.base.bounds.to_rectf();
let start_point = item.start_point.to_pointf();
let end_point = item.end_point.to_pointf();
let mut stops = Vec::new();
for stop in &item.stops {
stops.push(stop.to_gradient_stop());
}
builder.push_gradient(rect,
item.base.clip.to_clip_region(frame_builder),
start_point,
end_point,
stops,
&mut frame_builder.auxiliary_lists_builder);
}
DisplayItem::LineClass(..) => {
println!("TODO DisplayItem::LineClass");
}
DisplayItem::LayeredItemClass(..) => {
panic!("Unexpected in webrender!");
}
DisplayItem::BoxShadowClass(ref item) => {
let rect = item.base.bounds.to_rectf();
let box_bounds = item.box_bounds.to_rectf();
builder.push_box_shadow(rect,
item.base.clip.to_clip_region(frame_builder),
box_bounds,
item.offset.to_pointf(),
item.color.to_colorf(),
item.blur_radius.to_f32_px(),
item.spread_radius.to_f32_px(),
item.border_radius.to_f32_px(),
item.clip_mode.to_clip_mode());
}
DisplayItem::IframeClass(ref item) => {
let rect = item.base.bounds.to_rectf();
let pipeline_id = item.iframe.to_webrender();
builder.push_iframe(rect,
item.base.clip.to_clip_region(frame_builder),
pipeline_id);
}
DisplayItem::PushStackingContextClass(_) | DisplayItem::PopStackingContextClass(_) => {}
}
}
}
pub struct WebRenderFrameBuilder {
pub stacking_contexts: Vec<(webrender_traits::StackingContextId,
webrender_traits::StackingContext)>,
pub display_lists: Vec<(DisplayListId, webrender_traits::BuiltDisplayList)>,
pub auxiliary_lists_builder: AuxiliaryListsBuilder,
pub root_pipeline_id: PipelineId,
pub next_scroll_layer_id: usize,
}
impl WebRenderFrameBuilder {
pub fn new(root_pipeline_id: PipelineId) -> WebRenderFrameBuilder {
WebRenderFrameBuilder {
stacking_contexts: vec![],
display_lists: vec![],
auxiliary_lists_builder: AuxiliaryListsBuilder::new(),
root_pipeline_id: root_pipeline_id,
next_scroll_layer_id: 0,
}
}
pub fn add_stacking_context(&mut self,
api: &mut webrender_traits::RenderApi,
pipeline_id: PipelineId,
stacking_context: webrender_traits::StackingContext)
-> webrender_traits::StackingContextId {
assert!(pipeline_id == self.root_pipeline_id);
let id = api.next_stacking_context_id();
self.stacking_contexts.push((id, stacking_context));
id
}
pub fn add_display_list(&mut self,
api: &mut webrender_traits::RenderApi,
display_list: webrender_traits::BuiltDisplayList,
stacking_context: &mut webrender_traits::StackingContext)
-> DisplayListId {
let id = api.next_display_list_id();
stacking_context.has_stacking_contexts = stacking_context.has_stacking_contexts ||
display_list.descriptor().has_stacking_contexts;
stacking_context.display_lists.push(id);
self.display_lists.push((id, display_list));
id
}
pub fn next_scroll_layer_id(&mut self) -> webrender_traits::ScrollLayerId {
let scroll_layer_id = self.next_scroll_layer_id;
self.next_scroll_layer_id += 1;
webrender_traits::ScrollLayerId::new(self.root_pipeline_id, scroll_layer_id)
}
}
trait WebRenderStackingContextIdConverter {
fn convert_to_webrender(&self) -> webrender_traits::ServoStackingContextId;
}
impl WebRenderStackingContextIdConverter for StackingContextId {
fn convert_to_webrender(&self) -> webrender_traits::ServoStackingContextId {
webrender_traits::ServoStackingContextId(self.fragment_type().convert_to_webrender(),
self.id())
}
}
trait WebRenderFragmentTypeConverter {
fn convert_to_webrender(&self) -> webrender_traits::FragmentType;
}
impl WebRenderFragmentTypeConverter for FragmentType {
fn convert_to_webrender(&self) -> webrender_traits::FragmentType {
match *self {
FragmentType::FragmentBody => webrender_traits::FragmentType::FragmentBody,
FragmentType::BeforePseudoContent => {
webrender_traits::FragmentType::BeforePseudoContent
}
FragmentType::AfterPseudoContent => webrender_traits::FragmentType::AfterPseudoContent,
}
}
}