Bug 1532174 - Refactor WR fast transform, use when returning relative transforms r=gw
authorDzmitry Malyshau <dmalyshau@mozilla.com>
Wed, 08 May 2019 02:32:15 +0000
changeset 531811 5a44b50a07cfcbc201d54f9adab7993a5815c34d
parent 531810 93efde688b8c6869c30e2e50c9b755989d4e6e57
child 531812 efc64ba2b311e18d16820a00e98572807ee724a0
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1532174
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1532174 - Refactor WR fast transform, use when returning relative transforms r=gw Based on https://phabricator.services.mozilla.com/D30229 Instead of converting from the scale-offset to the transform right away in `get_relative_transform`, we only do it if there is a jump between coordinate spaces. Differential Revision: https://phabricator.services.mozilla.com/D30228
gfx/wr/webrender/src/clip_scroll_tree.rs
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/gpu_types.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/spatial_node.rs
gfx/wr/webrender/src/util.rs
--- a/gfx/wr/webrender/src/clip_scroll_tree.rs
+++ b/gfx/wr/webrender/src/clip_scroll_tree.rs
@@ -87,16 +87,27 @@ impl ops::Not for VisibleFace {
     fn not(self) -> Self {
         match self {
             VisibleFace::Front => VisibleFace::Back,
             VisibleFace::Back => VisibleFace::Front,
         }
     }
 }
 
+impl VisibleFace {
+    /// A convenient constructor from methods like `is_backface_visible()`
+    pub fn from_bool(is_backface: bool) -> Self {
+        if is_backface {
+            VisibleFace::Back
+        } else {
+            VisibleFace::Front
+        }
+    }
+}
+
 pub struct ClipScrollTree {
     /// Nodes which determine the positions (offsets and transforms) for primitives
     /// and clips.
     pub spatial_nodes: Vec<SpatialNode>,
 
     /// A list of transforms that establish new coordinate systems.
     /// Spatial nodes only establish a new coordinate system when
     /// they have a transform that is not a simple 2d translation.
@@ -137,20 +148,18 @@ pub struct TransformUpdateState {
     pub preserves_3d: bool,
 }
 
 /// A processed relative transform between two nodes in the clip-scroll tree.
 #[derive(Debug, Default)]
 pub struct RelativeTransform<U> {
     /// The flattened transform, produces Z = 0 at all times.
     pub flattened: TypedTransform3D<f32, LayoutPixel, U>,
-    /// Visible face of the original transform.
-    pub visible_face: VisibleFace,
     /// True if the original transform had perspective.
-    pub is_perspective: bool,
+    pub has_perspective: bool,
 }
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         ClipScrollTree {
             spatial_nodes: Vec::new(),
             coord_systems: Vec::new(),
             pending_scroll_offsets: FastHashMap::default(),
@@ -193,92 +202,80 @@ impl ClipScrollTree {
     pub fn get_relative_transform(
         &self,
         child_index: SpatialNodeIndex,
         parent_index: SpatialNodeIndex,
     ) -> RelativeTransform<LayoutPixel> {
         assert!(child_index.0 >= parent_index.0);
         let child = &self.spatial_nodes[child_index.0 as usize];
         let parent = &self.spatial_nodes[parent_index.0 as usize];
-
-        let mut visible_face = VisibleFace::Front;
-        let mut is_perspective = false;
+        let mut has_perspective = false;
 
         if child.coordinate_system_id == parent.coordinate_system_id {
             return RelativeTransform {
                 flattened: parent.coordinate_system_relative_scale_offset
                     .inverse()
                     .accumulate(&child.coordinate_system_relative_scale_offset)
                     .to_transform(),
-                visible_face,
-                is_perspective,
+                has_perspective,
             }
         }
 
         let mut coordinate_system_id = child.coordinate_system_id;
         let mut transform = child.coordinate_system_relative_scale_offset.to_transform();
         let mut transform_style = child.transform_style();
 
         // we need to update the associated parameters of a transform in two cases:
         // 1) when the flattening happens, so that we don't lose that original 3D aspects
         // 2) when we reach the end of iteration, so that our result is up to date
 
         while coordinate_system_id != parent.coordinate_system_id {
             let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
-            let transform_style_changed = coord_system.transform_style != transform_style;
 
             if coord_system.transform_style == TransformStyle::Flat {
-                is_perspective |= transform.has_perspective_component();
-                if transform_style_changed {
+                has_perspective |= transform.has_perspective_component();
+                if transform_style != TransformStyle::Flat {
                     //Note: this function makes the transform to ignore the Z coordinate of inputs
                     // *even* for computing the X and Y coordinates of the output.
                     //transform = transform.project_to_2d();
                     transform.m13 = 0.0;
                     transform.m23 = 0.0;
-                    transform.m33 = 0.0;
+                    transform.m33 = 1.0;
                     transform.m43 = 0.0;
                 }
             }
 
             coordinate_system_id = coord_system.parent.expect("invalid parent!");
             transform = transform.post_mul(&coord_system.transform);
             transform_style = coord_system.transform_style;
         }
 
-        visible_face = if transform.is_backface_visible() {
-            VisibleFace::Back
-        } else {
-            VisibleFace::Front
-        };
-
-        is_perspective |= transform.has_perspective_component();
+        has_perspective |= transform.has_perspective_component();
 
         transform = transform.post_mul(
             &parent.coordinate_system_relative_scale_offset
                 .inverse()
-                .to_transform()
+                .to_transform(),
         );
 
         RelativeTransform {
             flattened: transform,
-            visible_face,
-            is_perspective,
+            has_perspective,
         }
     }
 
     /// Calculate the relative transform from `child_index` to the scene root.
     pub fn get_world_transform(
         &self,
         index: SpatialNodeIndex,
     ) -> RelativeTransform<WorldPixel> {
         let relative = self.get_relative_transform(index, ROOT_SPATIAL_NODE_INDEX);
         RelativeTransform {
             flattened: relative.flattened.with_destination::<WorldPixel>(),
-            visible_face: relative.visible_face,
-            is_perspective: relative.is_perspective,
+            has_perspective: relative.has_perspective,
         }
     }
 
     /// Returns true if the spatial node is the same as the parent, or is
     /// a child of the parent.
     pub fn is_same_or_child_of(
         &self,
         spatial_node_index: SpatialNodeIndex,
@@ -577,18 +574,22 @@ impl ClipScrollTree {
 
     /// Get the visible face of the transfrom from the specified node to its parent.
     pub fn get_local_visible_face(&self, node_index: SpatialNodeIndex) -> VisibleFace {
         let node = &self.spatial_nodes[node_index.0 as usize];
         let parent_index = match node.parent {
             Some(index) => index,
             None => return VisibleFace::Front
         };
-        self.get_relative_transform(node_index, parent_index)
-            .visible_face
+        VisibleFace::from_bool(
+            self
+                .get_relative_transform(node_index, parent_index)
+                .flattened
+                .is_backface_visible()
+        )
     }
 
     #[allow(dead_code)]
     pub fn print(&self) {
         if !self.spatial_nodes.is_empty() {
             let mut pt = PrintTree::new("clip_scroll tree");
             self.print_with(&mut pt);
         }
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -636,17 +636,17 @@ impl FrameBuilder {
             device_rect: DeviceIntRect::new(
                 device_origin,
                 self.output_rect.size,
             ),
             background_color: self.background_color,
             layer,
             profile_counters,
             passes,
-            transform_palette: transform_palette.transforms,
+            transform_palette: transform_palette.finish(),
             render_tasks,
             deferred_resolves,
             gpu_cache_frame_id,
             has_been_rendered: false,
             has_texture_cache_tasks,
             prim_headers,
             recorded_dirty_regions: mem::replace(&mut scratch.recorded_dirty_regions, Vec::new()),
             debug_items: mem::replace(&mut scratch.debug_items, Vec::new()),
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -465,31 +465,35 @@ struct RelativeTransformKey {
 // Stores a contiguous list of TransformData structs, that
 // are ready for upload to the GPU.
 // TODO(gw): For now, this only stores the complete local
 //           to world transform for each spatial node. In
 //           the future, the transform palette will support
 //           specifying a coordinate system that the transform
 //           should be relative to.
 pub struct TransformPalette {
-    pub transforms: Vec<TransformData>,
+    transforms: Vec<TransformData>,
     metadata: Vec<TransformMetadata>,
     map: FastHashMap<RelativeTransformKey, usize>,
 }
 
 impl TransformPalette {
     pub fn new() -> Self {
         let _ = VECS_PER_TRANSFORM;
         TransformPalette {
             transforms: Vec::new(),
             metadata: Vec::new(),
             map: FastHashMap::default(),
         }
     }
 
+    pub fn finish(self) -> Vec<TransformData> {
+        self.transforms
+    }
+
     pub fn allocate(&mut self, count: usize) {
         self.transforms = vec![TransformData::invalid(); count];
         self.metadata = vec![TransformMetadata::invalid(); count];
     }
 
     pub fn set_world_transform(
         &mut self,
         index: SpatialNodeIndex,
@@ -540,34 +544,16 @@ impl TransformPalette {
                         child_index,
                         parent_index,
                         transform,
                     )
                 })
         }
     }
 
-    pub fn get_world_transform(
-        &self,
-        index: SpatialNodeIndex,
-    ) -> LayoutToWorldTransform {
-        self.transforms[index.0 as usize]
-            .transform
-            .with_destination::<WorldPixel>()
-    }
-
-    pub fn get_world_inv_transform(
-        &self,
-        index: SpatialNodeIndex,
-    ) -> WorldToLayoutTransform {
-        self.transforms[index.0 as usize]
-            .inv_transform
-            .with_source::<WorldPixel>()
-    }
-
     // Get a transform palette id for the given spatial node.
     // TODO(gw): In the future, it will be possible to specify
     //           a coordinate system id here, to allow retrieving
     //           transforms in the local space of a given spatial node.
     pub fn get_id(
         &mut self,
         from_index: SpatialNodeIndex,
         to_index: SpatialNodeIndex,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -14,17 +14,17 @@ use crate::clip_scroll_tree::{ROOT_SPATI
 use crate::debug_colors;
 use euclid::{size2, vec3, TypedPoint2D, TypedScale, TypedSize2D};
 use euclid::approxeq::ApproxEq;
 use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use crate::intern::ItemUid;
 use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
-use crate::gpu_types::{TransformPalette, UvRectKind};
+use crate::gpu_types::UvRectKind;
 use plane_split::{Clipper, Polygon, Splitter};
 use crate::prim_store::{CoordinateSpaceMapping, SpaceMapper};
 use crate::prim_store::{PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
 use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, VectorKey, PointKey};
 use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, RectangleKey};
 use crate::print_tree::PrintTreePrinter;
 use crate::render_backend::DataStores;
 use crate::render_task::{ClearMode, RenderTask, TileBlit};
@@ -2784,25 +2784,26 @@ impl PicturePrimitive {
         self.state.take().expect("bug: no state present!")
     }
 
     /// Add a primitive instance to the plane splitter. The function would generate
     /// an appropriate polygon, clip it against the frustum, and register with the
     /// given plane splitter.
     pub fn add_split_plane(
         splitter: &mut PlaneSplitter,
-        transforms: &TransformPalette,
+        clip_scroll_tree: &ClipScrollTree,
         prim_spatial_node_index: SpatialNodeIndex,
         original_local_rect: LayoutRect,
         combined_local_clip_rect: &LayoutRect,
         world_rect: WorldRect,
         plane_split_anchor: usize,
     ) -> bool {
-        let transform = transforms
-            .get_world_transform(prim_spatial_node_index);
+        let transform = clip_scroll_tree
+            .get_world_transform(prim_spatial_node_index)
+            .flattened;
         let matrix = transform.cast();
 
         // Apply the local clip rect here, before splitting. This is
         // because the local clip rect can't be applied in the vertex
         // shader for split composites, since we are drawing polygons
         // rather that rectangles. The interpolation still works correctly
         // since we determine the UVs by doing a bilerp with a factor
         // from the original local rect.
@@ -2810,18 +2811,21 @@ impl PicturePrimitive {
             .intersection(combined_local_clip_rect)
         {
             Some(rect) => rect.cast(),
             None => return false,
         };
         let world_rect = world_rect.cast();
 
         if transform.is_simple_translation() {
-            let inv_transform = transforms
-                .get_world_inv_transform(prim_spatial_node_index);
+            let inv_transform = clip_scroll_tree
+                .get_world_transform(prim_spatial_node_index)
+                .flattened
+                .inverse()
+                .expect("Simple translation, really?");
             let polygon = Polygon::from_transformed_rect_with_inverse(
                 local_rect,
                 &matrix,
                 &inv_transform.cast(),
                 plane_split_anchor,
             ).unwrap();
             splitter.add(polygon);
         } else {
@@ -2842,42 +2846,51 @@ impl PicturePrimitive {
         }
 
         true
     }
 
     pub fn resolve_split_planes(
         &mut self,
         splitter: &mut PlaneSplitter,
-        frame_state: &mut FrameBuildingState,
+        gpu_cache: &mut GpuCache,
+        clip_scroll_tree: &ClipScrollTree,
     ) {
         let ordered = match self.context_3d {
             Picture3DContext::In { root_data: Some(ref mut list), .. } => list,
             _ => panic!("Expected to find 3D context root"),
         };
         ordered.clear();
 
         // Process the accumulated split planes and order them for rendering.
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
             let spatial_node_index = self.prim_list.prim_instances[poly.anchor].spatial_node_index;
-            let transform = frame_state.transforms.get_world_inv_transform(spatial_node_index);
+            let transform = match clip_scroll_tree
+                .get_world_transform(spatial_node_index)
+                .flattened
+                .inverse()
+            {
+                Some(transform) => transform,
+                // logging this would be a bit too verbose
+                None => continue,
+            };
 
             let local_points = [
                 transform.transform_point3d(&poly.points[0].cast()).unwrap(),
                 transform.transform_point3d(&poly.points[1].cast()).unwrap(),
                 transform.transform_point3d(&poly.points[2].cast()).unwrap(),
                 transform.transform_point3d(&poly.points[3].cast()).unwrap(),
             ];
             let gpu_blocks = [
                 [local_points[0].x, local_points[0].y, local_points[1].x, local_points[1].y].into(),
                 [local_points[2].x, local_points[2].y, local_points[3].x, local_points[3].y].into(),
             ];
-            let gpu_handle = frame_state.gpu_cache.push_per_frame_blocks(&gpu_blocks);
-            let gpu_address = frame_state.gpu_cache.get_address(&gpu_handle);
+            let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
+            let gpu_address = gpu_cache.get_address(&gpu_handle);
 
             ordered.push(OrderedPictureChild {
                 anchor: poly.anchor,
                 spatial_node_index,
                 gpu_address,
             });
         }
     }
@@ -2997,17 +3010,17 @@ impl PicturePrimitive {
                     0.0
                 }
             };
 
             // Check if there is perspective, and thus whether a new
             // rasterization root should be established.
             let establishes_raster_root = frame_context.clip_scroll_tree
                 .get_relative_transform(surface_spatial_node_index, parent_raster_node_index)
-                .is_perspective;
+                .has_perspective;
 
             // Disallow subpixel AA if an intermediate surface is needed.
             // TODO(lsalzman): allow overriding parent if intermediate surface is opaque
             let allow_subpixel_aa = match composite_mode {
                 PictureCompositeMode::TileCache { clear_color, .. } => {
                     // If the tile cache has an opaque background, then it's fine to use
                     // subpixel rendering (this is the common case).
                     clear_color.a >= 1.0
@@ -3167,17 +3180,21 @@ impl PicturePrimitive {
         &mut self,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         data_stores: &mut DataStores,
     ) -> bool {
         let mut pic_state_for_children = self.take_state();
 
         if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
-            self.resolve_split_planes(splitter, frame_state);
+            self.resolve_split_planes(
+                splitter,
+                &mut frame_state.gpu_cache,
+                &frame_context.clip_scroll_tree,
+            );
         }
 
         let raster_config = match self.raster_config {
             Some(ref mut raster_config) => raster_config,
             None => {
                 return true
             }
         };
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -157,17 +157,17 @@ impl<F, T> CoordinateSpaceMapping<F, T> 
             (CoordinateSpaceMapping::ScaleOffset(scale_offset), VisibleFace::Front)
         } else {
             let relative = clip_scroll_tree
                 .get_relative_transform(target_node_index, ref_spatial_node_index);
             (
                 CoordinateSpaceMapping::Transform(
                     relative.flattened.with_source::<F>().with_destination::<T>()
                 ),
-                relative.visible_face,
+                VisibleFace::from_bool(relative.flattened.is_backface_visible())
             )
         }
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct SpaceMapper<F, T> {
     kind: CoordinateSpaceMapping<F, T>,
@@ -3083,17 +3083,17 @@ impl PrimitiveStore {
                 if pic.prepare_for_render(
                     frame_context,
                     frame_state,
                     data_stores,
                 ) {
                     if let Some(ref mut splitter) = pic_state.plane_splitter {
                         PicturePrimitive::add_split_plane(
                             splitter,
-                            frame_state.transforms,
+                            frame_context.clip_scroll_tree,
                             prim_instance.spatial_node_index,
                             pic.snapped_local_rect,
                             &prim_info.combined_local_clip_rect,
                             frame_context.screen_world_rect,
                             plane_split_anchor,
                         );
                     }
 
--- a/gfx/wr/webrender/src/spatial_node.rs
+++ b/gfx/wr/webrender/src/spatial_node.rs
@@ -303,32 +303,32 @@ impl SpatialNode {
                             external_id,
                             previous_spatial_nodes,
                         );
 
                         // Do a change-basis operation on the
                         // perspective matrix using the scroll offset.
                         source_transform
                             .pre_translate(&scroll_offset)
-                            .post_translate(-scroll_offset)
+                            .post_translate(&-scroll_offset)
                     }
                     ReferenceFrameKind::Perspective { scrolling_relative_to: None } |
                     ReferenceFrameKind::Transform => source_transform,
                 };
 
                 let resolved_transform =
                     LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
                         .pre_mul(&source_transform);
 
                 // The transformation for this viewport in world coordinates is the transformation for
                 // our parent reference frame, plus any accumulated scrolling offsets from nodes
                 // between our reference frame and this node. Finally, we also include
                 // whatever local transformation this reference frame provides.
                 let relative_transform = resolved_transform
-                    .post_translate(state.parent_accumulated_scroll_offset)
+                    .post_translate(&state.parent_accumulated_scroll_offset)
                     .to_transform()
                     .with_destination::<LayoutPixel>();
 
                 self.world_viewport_transform =
                     state.parent_reference_frame_transform.pre_mul(&relative_transform.into());
                 self.world_content_transform = self.world_viewport_transform;
 
                 info.invertible = self.world_viewport_transform.is_invertible();
--- a/gfx/wr/webrender/src/util.rs
+++ b/gfx/wr/webrender/src/util.rs
@@ -631,16 +631,29 @@ impl<Src, Dst> FastTransform<Src, Dst> {
     pub fn identity() -> Self {
         FastTransform::Offset(TypedVector2D::zero())
     }
 
     pub fn with_vector(offset: TypedVector2D<f32, Src>) -> Self {
         FastTransform::Offset(offset)
     }
 
+    #[allow(unused)]
+    pub fn with_scale_offset(scale_offset: ScaleOffset) -> Self {
+        if scale_offset.scale == Vector2D::new(1.0, 1.0) {
+            FastTransform::Offset(TypedVector2D::from_untyped(&scale_offset.offset))
+        } else {
+            FastTransform::Transform {
+                transform: scale_offset.to_transform(),
+                inverse: Some(scale_offset.inverse().to_transform()),
+                is_2d: true,
+            }
+        }
+    }
+
     #[inline(always)]
     pub fn with_transform(transform: TypedTransform3D<f32, Src, Dst>) -> Self {
         if transform.is_simple_2d_translation() {
             return FastTransform::Offset(TypedVector2D::new(transform.m41, transform.m42));
         }
         let inverse = transform.inverse();
         let is_2d = transform.is_2d();
         FastTransform::Transform { transform, inverse, is_2d}
@@ -678,43 +691,79 @@ impl<Src, Dst> FastTransform<Src, Dst> {
                 offset == TypedVector2D::zero()
             }
             FastTransform::Transform { ref transform, .. } => {
                 *transform == TypedTransform3D::identity()
             }
         }
     }
 
-    #[inline(always)]
+    pub fn post_mul<NewDst>(&self, other: &FastTransform<Dst, NewDst>) -> FastTransform<Src, NewDst> {
+        match *self {
+            FastTransform::Offset(offset) => match *other {
+                FastTransform::Offset(other_offset) => {
+                    FastTransform::Offset(offset + other_offset * TypedScale::<_, _, Src>::new(1.0))
+                }
+                FastTransform::Transform { transform: ref other_transform, .. } => {
+                    FastTransform::with_transform(
+                        other_transform
+                            .with_source::<Src>()
+                            .pre_translate(offset.to_3d())
+                    )
+                }
+            }
+            FastTransform::Transform { ref transform, ref inverse, is_2d } => match *other {
+                FastTransform::Offset(other_offset) => {
+                    FastTransform::with_transform(
+                        transform
+                            .post_translate(other_offset.to_3d())
+                            .with_destination::<NewDst>()
+                    )
+                }
+                FastTransform::Transform { transform: ref other_transform, inverse: ref other_inverse, is_2d: other_is_2d } => {
+                    FastTransform::Transform {
+                        transform: transform.post_mul(other_transform),
+                        inverse: inverse.as_ref().and_then(|self_inv|
+                            other_inverse.as_ref().map(|other_inv| self_inv.pre_mul(other_inv))
+                        ),
+                        is_2d: is_2d & other_is_2d,
+                    }
+                }
+            }
+        }
+    }
+
     pub fn pre_mul<NewSrc>(
         &self,
         other: &FastTransform<NewSrc, Src>
     ) -> FastTransform<NewSrc, Dst> {
-        match (self, other) {
-            (&FastTransform::Offset(ref offset), &FastTransform::Offset(ref other_offset)) => {
-                let offset = TypedVector2D::from_untyped(&offset.to_untyped());
-                FastTransform::Offset(offset + *other_offset)
-            }
-            _ => {
-                let new_transform = self.to_transform().pre_mul(&other.to_transform());
-                FastTransform::with_transform(new_transform)
-            }
-        }
+        other.post_mul(self)
     }
 
-    #[inline(always)]
     pub fn pre_translate(&self, other_offset: &TypedVector2D<f32, Src>) -> Self {
         match *self {
             FastTransform::Offset(ref offset) =>
                 FastTransform::Offset(*offset + *other_offset),
             FastTransform::Transform { transform, .. } =>
                 FastTransform::with_transform(transform.pre_translate(other_offset.to_3d()))
         }
     }
 
+    pub fn post_translate(&self, other_offset: &TypedVector2D<f32, Dst>) -> Self {
+        match *self {
+            FastTransform::Offset(ref offset) => {
+                FastTransform::Offset(*offset + *other_offset * TypedScale::<_, _, Src>::new(1.0))
+            }
+            FastTransform::Transform { ref transform, .. } => {
+                let transform = transform.post_translate(other_offset.to_3d());
+                FastTransform::with_transform(transform)
+            }
+        }
+    }
+
     #[inline(always)]
     pub fn project_to_2d(&self) -> Self {
         match *self {
             FastTransform::Offset(..) => self.clone(),
             FastTransform::Transform { ref transform, .. } => FastTransform::with_transform(transform.project_to_2d()),
         }
     }
 
@@ -735,29 +784,16 @@ impl<Src, Dst> FastTransform<Src, Dst> {
             FastTransform::Offset(offset) => {
                 let new_point = *point + offset;
                 Some(TypedPoint2D::from_untyped(&new_point.to_untyped()))
             }
             FastTransform::Transform { ref transform, .. } => transform.transform_point2d(point),
         }
     }
 
-    pub fn post_translate(&self, new_offset: TypedVector2D<f32, Dst>) -> Self {
-        match *self {
-            FastTransform::Offset(offset) => {
-                let offset = offset.to_untyped() + new_offset.to_untyped();
-                FastTransform::Offset(TypedVector2D::from_untyped(&offset))
-            }
-            FastTransform::Transform { ref transform, .. } => {
-                let transform = transform.post_translate(new_offset.to_3d());
-                FastTransform::with_transform(transform)
-            }
-        }
-    }
-
     #[inline(always)]
     pub fn inverse(&self) -> Option<FastTransform<Dst, Src>> {
         match *self {
             FastTransform::Offset(offset) =>
                 Some(FastTransform::Offset(TypedVector2D::new(-offset.x, -offset.y))),
             FastTransform::Transform { transform, inverse: Some(inverse), is_2d, } =>
                 Some(FastTransform::Transform {
                     transform: inverse,