Bug 1574493 - Part 4. Snap primitives during scene building. r=kvark

Now that rounding has been removed from Gecko, we need to start snapping
properly in WebRender. Snapping can change the size of a primitive, and
thus it is problematic to do any later than scene building due to the
GPU caching and sharing of data between clips and such that only differ
in their positioning.

This patch produces a snapping transform which allows any primitive to
snap using information known during scene building. This excludes
animated tranforms which are assumed to be the identity. This allows for
primitives that are marked as will-change: transform but given no
initial transform to render the same as primitives that are not. This
also excludes scroll positioning because that is not known until frame
building. A follow up patch will deal with that.

Differential Revision: https://phabricator.services.mozilla.com/D45059

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Andrew Osmond 2019-09-13 14:03:25 +00:00
parent 25866edb78
commit b39a6837b4
8 changed files with 376 additions and 39 deletions

View File

@ -98,10 +98,13 @@ impl<'a> DisplayListFlattener<'a> {
// Apply parameters that affect where the shadow rect
// exists in the local space of the primitive.
let shadow_rect = prim_info
.rect
.translate(*box_offset)
.inflate(spread_amount, spread_amount);
let shadow_rect = self.snap_rect(
&prim_info
.rect
.translate(*box_offset)
.inflate(spread_amount, spread_amount),
clip_and_scroll.spatial_node_index,
);
// If blur radius is zero, we can use a fast path with
// no blur applied.

View File

@ -1002,9 +1002,9 @@ impl ClipItemKeyKind {
}
}
pub fn image_mask(image_mask: &ImageMask) -> Self {
pub fn image_mask(image_mask: &ImageMask, mask_rect: LayoutRect) -> Self {
ClipItemKeyKind::ImageMask(
image_mask.rect.into(),
mask_rect.into(),
image_mask.image,
image_mask.repeat,
)

View File

@ -570,12 +570,16 @@ impl ClipScrollTree {
self.add_spatial_node(node)
}
pub fn add_spatial_node(&mut self, node: SpatialNode) -> SpatialNodeIndex {
pub fn add_spatial_node(&mut self, mut node: SpatialNode) -> SpatialNodeIndex {
let index = SpatialNodeIndex::new(self.spatial_nodes.len());
// When the parent node is None this means we are adding the root.
if let Some(parent_index) = node.parent {
self.spatial_nodes[parent_index.0 as usize].add_child(index);
let parent_node = &mut self.spatial_nodes[parent_index.0 as usize];
parent_node.add_child(index);
node.update_snapping(Some(parent_node));
} else {
node.update_snapping(None);
}
self.spatial_nodes.push(node);
@ -640,12 +644,15 @@ impl ClipScrollTree {
pt.new_level(format!("ReferenceFrame"));
pt.add_item(format!("kind: {:?}", info.kind));
pt.add_item(format!("transform_style: {:?}", info.transform_style));
pt.add_item(format!("source_transform: {:?}", info.source_transform));
pt.add_item(format!("origin_in_parent_reference_frame: {:?}", info.origin_in_parent_reference_frame));
}
}
pt.add_item(format!("index: {:?}", index));
pt.add_item(format!("content_transform: {:?}", node.content_transform));
pt.add_item(format!("viewport_transform: {:?}", node.viewport_transform));
pt.add_item(format!("snapping_transform: {:?}", node.snapping_transform));
pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
for child_index in &node.children {

View File

@ -27,6 +27,7 @@ use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveSto
use crate::prim_store::{ScrollNodeAndClipChain, PictureIndex};
use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex};
use crate::prim_store::{register_prim_chase_id, get_line_decoration_sizes};
use crate::prim_store::{SpaceSnapper};
use crate::prim_store::backdrop::Backdrop;
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams};
@ -338,10 +339,13 @@ impl<'a> DisplayListFlattener<'a> {
found_explicit_tile_cache: false,
};
let device_pixel_scale = view.accumulated_scale_factor_for_snapping();
flattener.push_root(
root_pipeline_id,
&root_pipeline.viewport_size,
&root_pipeline.content_size,
device_pixel_scale,
);
// In order to ensure we have a single root stacking context for the
@ -365,6 +369,7 @@ impl<'a> DisplayListFlattener<'a> {
ClipChainId::NONE,
RasterSpace::Screen,
/* is_backdrop_root = */ true,
device_pixel_scale,
);
flattener.flatten_items(
@ -935,6 +940,7 @@ impl<'a> DisplayListFlattener<'a> {
clip_chain_id,
stacking_context.raster_space,
stacking_context.is_backdrop_root,
self.sc_stack.last().unwrap().snap_to_device.device_pixel_scale,
);
if cfg!(debug_assertions) && apply_pipeline_clip && clip_chain_id != ClipChainId::NONE {
@ -992,8 +998,18 @@ impl<'a> DisplayListFlattener<'a> {
);
self.pipeline_clip_chain_stack.push(clip_chain_index);
let bounds = info.bounds;
let origin = current_offset + bounds.origin.to_vector();
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
snap_to_device.set_target_spatial_node(
spatial_node_index,
self.clip_scroll_tree,
);
let bounds = snap_to_device.snap_rect(
&info.bounds.translate(current_offset),
);
let content_size = snap_to_device.snap_size(&pipeline.content_size);
let spatial_node_index = self.push_reference_frame(
SpatialId::root_reference_frame(iframe_pipeline_id),
Some(spatial_node_index),
@ -1001,7 +1017,7 @@ impl<'a> DisplayListFlattener<'a> {
TransformStyle::Flat,
PropertyBinding::Value(LayoutTransform::identity()),
ReferenceFrameKind::Transform,
origin,
bounds.origin.to_vector(),
);
let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size);
@ -1011,7 +1027,7 @@ impl<'a> DisplayListFlattener<'a> {
Some(ExternalScrollId(0, iframe_pipeline_id)),
iframe_pipeline_id,
&iframe_rect,
&pipeline.content_size,
&content_size,
ScrollSensitivity::ScriptAndInputEvents,
ScrollFrameKind::PipelineRoot,
LayoutVector2D::zero(),
@ -1055,7 +1071,12 @@ impl<'a> DisplayListFlattener<'a> {
common: &CommonItemProperties,
apply_pipeline_clip: bool
) -> (LayoutPrimitiveInfo, ScrollNodeAndClipChain) {
self.process_common_properties_with_bounds(common, &common.clip_rect, apply_pipeline_clip)
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
common,
&common.clip_rect,
apply_pipeline_clip,
);
(layout, clip_and_scroll)
}
fn process_common_properties_with_bounds(
@ -1063,7 +1084,7 @@ impl<'a> DisplayListFlattener<'a> {
common: &CommonItemProperties,
bounds: &LayoutRect,
apply_pipeline_clip: bool
) -> (LayoutPrimitiveInfo, ScrollNodeAndClipChain) {
) -> (LayoutPrimitiveInfo, LayoutRect, ScrollNodeAndClipChain) {
let clip_and_scroll = self.get_clip_and_scroll(
&common.clip_id,
&common.spatial_id,
@ -1072,16 +1093,36 @@ impl<'a> DisplayListFlattener<'a> {
let current_offset = self.current_offset(clip_and_scroll.spatial_node_index);
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
snap_to_device.set_target_spatial_node(
clip_and_scroll.spatial_node_index,
self.clip_scroll_tree
);
let clip_rect = common.clip_rect.translate(current_offset);
let rect = bounds.translate(current_offset);
let layout = LayoutPrimitiveInfo {
rect,
clip_rect,
rect: snap_to_device.snap_rect(&rect),
clip_rect: snap_to_device.snap_rect(&clip_rect),
is_backface_visible: common.is_backface_visible,
hit_info: common.hit_info,
};
(layout, clip_and_scroll)
(layout, rect, clip_and_scroll)
}
pub fn snap_rect(
&mut self,
rect: &LayoutRect,
target_spatial_node: SpatialNodeIndex,
) -> LayoutRect {
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
snap_to_device.set_target_spatial_node(
target_spatial_node,
self.clip_scroll_tree
);
snap_to_device.snap_rect(rect)
}
fn flatten_item<'b>(
@ -1092,7 +1133,7 @@ impl<'a> DisplayListFlattener<'a> {
) -> Option<BuiltDisplayListIter<'a>> {
match *item.item() {
DisplayItem::Image(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.bounds,
apply_pipeline_clip,
@ -1111,16 +1152,22 @@ impl<'a> DisplayListFlattener<'a> {
);
}
DisplayItem::RepeatingImage(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.bounds,
apply_pipeline_clip,
);
let stretch_size = process_repeat_size(
&layout.rect,
&unsnapped_rect,
info.stretch_size,
);
self.add_image(
clip_and_scroll,
&layout,
info.stretch_size,
stretch_size,
info.tile_spacing,
None,
info.image_key,
@ -1130,7 +1177,7 @@ impl<'a> DisplayListFlattener<'a> {
);
}
DisplayItem::YuvImage(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.bounds,
apply_pipeline_clip,
@ -1147,7 +1194,7 @@ impl<'a> DisplayListFlattener<'a> {
);
}
DisplayItem::Text(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.bounds,
apply_pipeline_clip,
@ -1198,7 +1245,7 @@ impl<'a> DisplayListFlattener<'a> {
);
}
DisplayItem::Line(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.area,
apply_pipeline_clip,
@ -1214,19 +1261,25 @@ impl<'a> DisplayListFlattener<'a> {
);
}
DisplayItem::Gradient(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.bounds,
apply_pipeline_clip,
);
let tile_size = process_repeat_size(
&layout.rect,
&unsnapped_rect,
info.tile_size,
);
if let Some(prim_key_kind) = self.create_linear_gradient_prim(
&layout,
info.gradient.start_point,
info.gradient.end_point,
item.gradient_stops(),
info.gradient.extend_mode,
info.tile_size,
tile_size,
info.tile_spacing,
None,
) {
@ -1239,12 +1292,18 @@ impl<'a> DisplayListFlattener<'a> {
}
}
DisplayItem::RadialGradient(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, unsnapped_rect, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.bounds,
apply_pipeline_clip,
);
let tile_size = process_repeat_size(
&layout.rect,
&unsnapped_rect,
info.tile_size,
);
let prim_key_kind = self.create_radial_gradient_prim(
&layout,
info.gradient.center,
@ -1253,7 +1312,7 @@ impl<'a> DisplayListFlattener<'a> {
info.gradient.radius.width / info.gradient.radius.height,
item.gradient_stops(),
info.gradient.extend_mode,
info.tile_size,
tile_size,
info.tile_spacing,
None,
);
@ -1266,7 +1325,7 @@ impl<'a> DisplayListFlattener<'a> {
);
}
DisplayItem::BoxShadow(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.box_bounds,
apply_pipeline_clip,
@ -1284,7 +1343,7 @@ impl<'a> DisplayListFlattener<'a> {
);
}
DisplayItem::Border(ref info) => {
let (layout, clip_and_scroll) = self.process_common_properties_with_bounds(
let (layout, _, clip_and_scroll) = self.process_common_properties_with_bounds(
&info.common,
&info.bounds,
apply_pipeline_clip,
@ -1701,6 +1760,7 @@ impl<'a> DisplayListFlattener<'a> {
clip_chain_id: ClipChainId,
requested_raster_space: RasterSpace,
is_backdrop_root: bool,
device_pixel_scale: DevicePixelScale,
) {
// Check if this stacking context is the root of a pipeline, and the caller
// has requested it as an output frame.
@ -1803,6 +1863,14 @@ impl<'a> DisplayListFlattener<'a> {
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
}
let snap_to_device = self.sc_stack.last().map_or(
SpaceSnapper::new(
ROOT_SPATIAL_NODE_INDEX,
device_pixel_scale,
),
|sc| sc.snap_to_device.clone(),
);
// Push the SC onto the stack, so we know how to handle things in
// pop_stacking_context.
self.sc_stack.push(FlattenedStackingContext {
@ -1819,6 +1887,7 @@ impl<'a> DisplayListFlattener<'a> {
context_3d,
create_tile_cache,
is_backdrop_root,
snap_to_device,
});
}
@ -2194,6 +2263,7 @@ impl<'a> DisplayListFlattener<'a> {
pipeline_id: PipelineId,
viewport_size: &LayoutSize,
content_size: &LayoutSize,
device_pixel_scale: DevicePixelScale,
) {
if let ChasePrimitive::Id(id) = self.config.chase_primitive {
println!("Chasing {:?} by index", id);
@ -2212,13 +2282,27 @@ impl<'a> DisplayListFlattener<'a> {
LayoutVector2D::zero(),
);
// We can't use this with the stacking context because it does not exist
// yet. Just create a dedicated snapper for the root.
let snap_to_device = SpaceSnapper::new_with_target(
spatial_node_index,
ROOT_SPATIAL_NODE_INDEX,
device_pixel_scale,
self.clip_scroll_tree,
);
let content_size = snap_to_device.snap_size(content_size);
let viewport_rect = snap_to_device.snap_rect(
&LayoutRect::new(LayoutPoint::zero(), *viewport_size),
);
self.add_scroll_frame(
SpatialId::root_scroll_node(pipeline_id),
spatial_node_index,
Some(ExternalScrollId(0, pipeline_id)),
pipeline_id,
&LayoutRect::new(LayoutPoint::zero(), *viewport_size),
content_size,
&viewport_rect,
&content_size,
ScrollSensitivity::ScriptAndInputEvents,
ScrollFrameKind::PipelineRoot,
LayoutVector2D::zero(),
@ -2242,6 +2326,14 @@ impl<'a> DisplayListFlattener<'a> {
// Map the ClipId for the positioning node to a spatial node index.
let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id);
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
snap_to_device.set_target_spatial_node(
spatial_node_index,
self.clip_scroll_tree,
);
let snapped_clip_rect = snap_to_device.snap_rect(&clip_region.main);
let mut clip_count = 0;
// Intern each clip item in this clip node, and add the interned
@ -2251,7 +2343,7 @@ impl<'a> DisplayListFlattener<'a> {
// Build the clip sources from the supplied region.
let item = ClipItemKey {
kind: ClipItemKeyKind::rectangle(clip_region.main, ClipMode::Clip),
kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip),
spatial_node_index,
};
let handle = self
@ -2268,8 +2360,9 @@ impl<'a> DisplayListFlattener<'a> {
clip_count += 1;
if let Some(ref image_mask) = clip_region.image_mask {
let snapped_mask_rect = snap_to_device.snap_rect(&image_mask.rect);
let item = ClipItemKey {
kind: ClipItemKeyKind::image_mask(image_mask),
kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect),
spatial_node_index,
};
@ -2288,9 +2381,10 @@ impl<'a> DisplayListFlattener<'a> {
}
for region in clip_region.complex_clips {
let snapped_region_rect = snap_to_device.snap_rect(&region.rect);
let item = ClipItemKey {
kind: ClipItemKeyKind::rounded_rect(
region.rect,
snapped_region_rect,
region.radii,
region.mode,
),
@ -2563,11 +2657,23 @@ impl<'a> DisplayListFlattener<'a> {
P: InternablePrimitive + CreateShadow,
Interners: AsMut<Interner<P>>,
{
// Offset the local rect and clip rect by the shadow offset.
let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device;
snap_to_device.set_target_spatial_node(
pending_primitive.clip_and_scroll.spatial_node_index,
self.clip_scroll_tree
);
// Offset the local rect and clip rect by the shadow offset. The pending
// primitive has already been snapped, but we will need to snap the
// shadow after translation. We don't need to worry about the size
// changing because the shadow has the same raster space as the
// primitive, and thus we know the size is already rounded.
let mut info = pending_primitive.info.clone();
info.rect = info.rect.translate(pending_shadow.shadow.offset);
info.clip_rect = info.clip_rect.translate(
pending_shadow.shadow.offset
info.rect = snap_to_device.snap_rect(
&info.rect.translate(pending_shadow.shadow.offset),
);
info.clip_rect = snap_to_device.snap_rect(
&info.clip_rect.translate(pending_shadow.shadow.offset),
);
// Construct and add a primitive for the given shadow.
@ -3456,6 +3562,13 @@ struct FlattenedStackingContext {
/// True if this stacking context is a backdrop root.
is_backdrop_root: bool,
/// A helper struct to snap local rects in device space. During frame
/// building we may establish new raster roots, however typically that is in
/// cases where we won't be applying snapping (e.g. has perspective), or in
/// edge cases (e.g. SVG filter) where we can accept slightly incorrect
/// behaviour in favour of getting the common case right.
snap_to_device: SpaceSnapper,
}
impl FlattenedStackingContext {
@ -3709,3 +3822,22 @@ fn filter_primitives_for_compositing(
// more efficient than cloning these here.
input_filter_primitives.iter().map(|primitive| primitive.into()).collect()
}
fn process_repeat_size(
snapped_rect: &LayoutRect,
unsnapped_rect: &LayoutRect,
repeat_size: LayoutSize,
) -> LayoutSize {
LayoutSize::new(
if repeat_size.width == unsnapped_rect.size.width {
snapped_rect.size.width
} else {
repeat_size.width
},
if repeat_size.height == unsnapped_rect.size.height {
snapped_rect.size.height
} else {
repeat_size.height
},
)
}

View File

@ -52,7 +52,7 @@ use std::{cmp, fmt, hash, ops, u32, usize, mem};
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::storage;
use crate::texture_cache::TEXTURE_REGION_DIMENSIONS;
use crate::util::{MatrixHelpers, MaxRect, Recycler};
use crate::util::{MatrixHelpers, MaxRect, Recycler, ScaleOffset, RectHelpers};
use crate::util::{clamp_to_scale_factor, pack_as_float, project_rect, raster_rect_to_device_pixels};
use crate::internal_types::{LayoutPrimitiveInfo, Filter};
use smallvec::SmallVec;
@ -132,6 +132,91 @@ impl PrimitiveOpacity {
}
}
#[derive(Clone, Debug)]
pub struct SpaceSnapper {
pub ref_spatial_node_index: SpatialNodeIndex,
current_target_spatial_node_index: SpatialNodeIndex,
snapping_transform: Option<ScaleOffset>,
pub device_pixel_scale: DevicePixelScale,
}
impl SpaceSnapper {
pub fn new(
ref_spatial_node_index: SpatialNodeIndex,
device_pixel_scale: DevicePixelScale,
) -> Self {
SpaceSnapper {
ref_spatial_node_index,
current_target_spatial_node_index: SpatialNodeIndex::INVALID,
snapping_transform: None,
device_pixel_scale,
}
}
pub fn new_with_target(
ref_spatial_node_index: SpatialNodeIndex,
target_node_index: SpatialNodeIndex,
device_pixel_scale: DevicePixelScale,
clip_scroll_tree: &ClipScrollTree,
) -> Self {
let mut snapper = SpaceSnapper {
ref_spatial_node_index,
current_target_spatial_node_index: SpatialNodeIndex::INVALID,
snapping_transform: None,
device_pixel_scale,
};
snapper.set_target_spatial_node(target_node_index, clip_scroll_tree);
snapper
}
pub fn set_target_spatial_node(
&mut self,
target_node_index: SpatialNodeIndex,
clip_scroll_tree: &ClipScrollTree,
) {
if target_node_index == self.current_target_spatial_node_index {
return
}
let ref_spatial_node = &clip_scroll_tree.spatial_nodes[self.ref_spatial_node_index.0 as usize];
let target_spatial_node = &clip_scroll_tree.spatial_nodes[target_node_index.0 as usize];
self.current_target_spatial_node_index = target_node_index;
self.snapping_transform = match (ref_spatial_node.snapping_transform, target_spatial_node.snapping_transform) {
(Some(ref ref_scale_offset), Some(ref target_scale_offset)) => {
Some(ref_scale_offset
.inverse()
.accumulate(target_scale_offset)
.scale(self.device_pixel_scale.0))
}
_ => None,
};
}
pub fn snap_rect<F>(&self, rect: &Rect<f32, F>) -> Rect<f32, F> where F: fmt::Debug {
debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID);
match self.snapping_transform {
Some(ref scale_offset) => {
let snapped_device_rect : DeviceRect = scale_offset.map_rect(rect).snap();
scale_offset.unmap_rect(&snapped_device_rect)
}
None => *rect,
}
}
pub fn snap_size<F>(&self, size: &Size2D<f32, F>) -> Size2D<f32, F> where F: fmt::Debug {
debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID);
match self.snapping_transform {
Some(ref scale_offset) => {
let rect = Rect::<f32, F>::new(Point2D::<f32, F>::zero(), *size);
let snapped_device_rect : DeviceRect = scale_offset.map_rect(&rect).snap();
scale_offset.unmap_rect(&snapped_device_rect).size
}
None => *size,
}
}
}
#[derive(Debug, Clone)]
pub struct SpaceMapper<F, T> {

View File

@ -84,6 +84,13 @@ impl DocumentView {
self.pinch_zoom_factor
)
}
pub fn accumulated_scale_factor_for_snapping(&self) -> DevicePixelScale {
DevicePixelScale::new(
self.device_pixel_ratio *
self.page_zoom_factor
)
}
}
#[derive(Copy, Clone, Hash, MallocSizeOf, PartialEq, PartialOrd, Debug, Eq, Ord)]
@ -1750,6 +1757,8 @@ impl RenderBackend {
config.serialize(doc.frame_builder.as_ref().unwrap(), file_name);
let file_name = format!("scratch-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.scratch, file_name);
let file_name = format!("properties-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.dynamic_properties, file_name);
let file_name = format!("render-tasks-{}-{}.svg", id.namespace_id.0, id.id);
let mut svg_file = fs::File::create(&config.file_path(file_name, "svg"))
.expect("Failed to open the SVG file.");

View File

@ -38,6 +38,10 @@ pub struct SpatialNode {
/// Content scale/offset relative to the coordinate system.
pub content_transform: ScaleOffset,
/// Snapping scale/offset relative to the coordinate system. If None, then
/// we should not snap entities bound to this spatial node.
pub snapping_transform: Option<ScaleOffset>,
/// The axis-aligned coordinate system id of this node.
pub coordinate_system_id: CoordinateSystemId,
@ -111,6 +115,7 @@ impl SpatialNode {
SpatialNode {
viewport_transform: ScaleOffset::identity(),
content_transform: ScaleOffset::identity(),
snapping_transform: None,
coordinate_system_id: CoordinateSystemId(0),
transform_kind: TransformedRectKind::AxisAligned,
parent: parent_index,
@ -635,6 +640,55 @@ impl SpatialNode {
}
}
/// Updates the snapping transform.
pub fn update_snapping(
&mut self,
parent: Option<&SpatialNode>,
) {
// Reset in case of an early return.
self.snapping_transform = None;
// We need to incorporate the parent scale/offset with the child.
// If the parent does not have a scale/offset, then we know we are
// not 2d axis aligned and thus do not need to snap its children
// either.
let parent_scale_offset = match parent {
Some(parent) => {
match parent.snapping_transform {
Some(scale_offset) => scale_offset,
None => return,
}
},
_ => ScaleOffset::identity(),
};
let scale_offset = match self.node_type {
SpatialNodeType::ReferenceFrame(ref info) => {
match info.source_transform {
PropertyBinding::Value(ref value) => {
// We can only get a ScaleOffset if the transform is 2d axis
// aligned.
match ScaleOffset::from_transform(value) {
Some(scale_offset) => {
let origin_offset = info.origin_in_parent_reference_frame;
ScaleOffset::from_offset(origin_offset.to_untyped())
.accumulate(&scale_offset)
}
None => return,
}
}
// Assume animations start at the identity transform for snapping purposes.
// TODO(aosmond): Is there a better known starting point?
PropertyBinding::Binding(..) => ScaleOffset::identity(),
}
}
_ => ScaleOffset::identity(),
};
self.snapping_transform = Some(parent_scale_offset.accumulate(&scale_offset));
}
/// Returns true for ReferenceFrames whose source_transform is
/// bound to the property binding id.
pub fn is_transform_bound_to_property(&self, id: PropertyBindingId) -> bool {

View File

@ -166,6 +166,13 @@ impl ScaleOffset {
})
}
pub fn from_offset(offset: default::Vector2D<f32>) -> Self {
ScaleOffset {
scale: Vector2D::new(1.0, 1.0),
offset,
}
}
pub fn inverse(&self) -> Self {
ScaleOffset {
scale: Vector2D::new(
@ -188,6 +195,15 @@ impl ScaleOffset {
)
}
pub fn scale(&self, scale: f32) -> Self {
self.accumulate(
&ScaleOffset {
scale: Vector2D::new(scale, scale),
offset: Vector2D::zero(),
}
)
}
/// Produce a ScaleOffset that includes both self and other.
/// The 'self' ScaleOffset is applied after other.
/// This is equivalent to `Transform3D::pre_transform`.
@ -405,12 +421,29 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> for Transform3D<f32, Src, Dst> {
}
}
pub trait PointHelpers<U>
where
Self: Sized,
{
fn snap(&self) -> Self;
}
impl<U> PointHelpers<U> for Point2D<f32, U> {
fn snap(&self) -> Self {
Point2D::new(
(self.x + 0.5).floor(),
(self.y + 0.5).floor(),
)
}
}
pub trait RectHelpers<U>
where
Self: Sized,
{
fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
fn is_well_formed_and_nonempty(&self) -> bool;
fn snap(&self) -> Self;
}
impl<U> RectHelpers<U> for Rect<f32, U> {
@ -424,6 +457,20 @@ impl<U> RectHelpers<U> for Rect<f32, U> {
fn is_well_formed_and_nonempty(&self) -> bool {
self.size.width > 0.0 && self.size.height > 0.0
}
fn snap(&self) -> Self {
let origin = Point2D::new(
(self.origin.x + 0.5).floor(),
(self.origin.y + 0.5).floor(),
);
Rect::new(
origin,
Size2D::new(
(self.origin.x + self.size.width + 0.5).floor() - origin.x,
(self.origin.y + self.size.height + 0.5).floor() - origin.y,
),
)
}
}
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {