[cc] Support 2D scales in raster transforms

Before this patch, AxisTransform2d used to store the transform scale as
a single float, and then all the logic was 1D. This produced suboptimal
results for things like 'transform: scale3d(1, 5, 1)'.

This patch changes AxisTransform2d to store the scale as a Vector2dF,
and updates PictureLayerImpl to use 2D logic. Most of the tiling logic
is kept unchanged, using the maximum component of the scale.

Note that the ideal scale continues clamping the maximum scale component
to not be greater than 5 times the minimum one.

Bug: 1119996

TEST=third_party/blink/web_tests/compositing/transform-3d-scales-different-x-y.html

Change-Id: I13fb3605455393d4a65c788103ba28b923e1cb4a
Cq-Do-Not-Cancel-Tryjobs: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2642377
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: Victor Miura <vmiura@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: vmpstr <vmpstr@chromium.org>
Reviewed-by: Xianzhu Wang <wangxianzhu@chromium.org>
Commit-Queue: Oriol Brufau <obrufau@igalia.com>
Cr-Commit-Position: refs/heads/master@{#872117}
diff --git a/cc/benchmarks/rasterize_and_record_benchmark_impl.cc b/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
index 5532530..ff02f46 100644
--- a/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
+++ b/cc/benchmarks/rasterize_and_record_benchmark_impl.cc
@@ -8,6 +8,7 @@
 
 #include <algorithm>
 #include <limits>
+#include <memory>
 
 #include "base/timer/lap_timer.h"
 #include "base/values.h"
@@ -31,7 +32,7 @@
 void RunBenchmark(RasterSource* raster_source,
                   ImageDecodeCache* image_decode_cache,
                   const gfx::Rect& content_rect,
-                  float contents_scale,
+                  const gfx::Vector2dF& contents_scale,
                   size_t repeat_count,
                   base::TimeDelta* min_time,
                   bool* is_solid_color) {
@@ -48,8 +49,8 @@
                          base::TimeDelta::FromMilliseconds(kTimeLimitMillis),
                          kTimeCheckInterval);
     SkColor color = SK_ColorTRANSPARENT;
-    gfx::Rect layer_rect =
-        gfx::ScaleToEnclosingRect(content_rect, 1.f / contents_scale);
+    gfx::Rect layer_rect = gfx::ScaleToEnclosingRect(
+        content_rect, 1.f / contents_scale.x(), 1.f / contents_scale.y());
     *is_solid_color =
         raster_source->PerformSolidColorAnalysis(layer_rect, &color);
 
@@ -230,7 +231,7 @@
     DCHECK(*it);
 
     gfx::Rect content_rect = (*it)->content_rect();
-    float contents_scale = (*it)->raster_transform().scale();
+    const gfx::Vector2dF& contents_scale = (*it)->raster_transform().scale();
 
     base::TimeDelta min_time;
     bool is_solid_color = false;
diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc
index e755b3b..8501332 100644
--- a/cc/layers/heads_up_display_layer_impl.cc
+++ b/cc/layers/heads_up_display_layer_impl.cc
@@ -191,7 +191,8 @@
   }
 
   int max_texture_size = layer_tree_impl()->max_texture_size();
-  internal_contents_scale_ = GetIdealContentsScale();
+  // TODO(crbug.com/1196414): Support 2D scales in heads up layers.
+  internal_contents_scale_ = GetIdealContentsScaleKey();
   internal_content_bounds_ =
       gfx::ScaleToCeiledSize(bounds(), internal_contents_scale_);
   internal_content_bounds_.SetToMin(
@@ -379,12 +380,13 @@
                               can_use_lcd_text, gfx::ColorSpace::CreateSRGB(),
                               backing->mailbox.name);
       gfx::Vector2dF post_translate(0.f, 0.f);
+      gfx::Vector2dF post_scale(1.f, 1.f);
       DummyImageProvider image_provider;
       size_t max_op_size_limit =
           gpu::raster::RasterInterface::kDefaultMaxOpSizeHint;
       ri->RasterCHROMIUM(display_item_list.get(), &image_provider, size,
                          gfx::Rect(size), gfx::Rect(size), post_translate,
-                         1.f /* post_scale */, false /* requires_clear */,
+                         post_scale, false /* requires_clear */,
                          &max_op_size_limit);
       ri->EndRasterCHROMIUM();
       backing->mailbox_sync_token =
diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc
index 29fb9db..9dbfd59 100644
--- a/cc/layers/layer_impl.cc
+++ b/cc/layers/layer_impl.cc
@@ -791,7 +791,7 @@
   return GetEffectTree().GetRenderSurface(render_target_effect_tree_index());
 }
 
-float LayerImpl::GetIdealContentsScale() const {
+gfx::Vector2dF LayerImpl::GetIdealContentsScale() const {
   float page_scale = IsAffectedByPageScale()
                          ? layer_tree_impl()->current_page_scale_factor()
                          : 1.f;
@@ -801,6 +801,7 @@
 
   const auto& transform = ScreenSpaceTransform();
   if (transform.HasPerspective()) {
+    // TODO(crbug.com/1196414): This function should return a 2D scale.
     float scale = gfx::ComputeApproximateMaxScale(transform);
 
     const int kMaxTilesToCoverLayerDimension = 5;
@@ -827,13 +828,21 @@
     scale = std::round(scale);
 
     // Don't let the scale fall below the default scale.
-    return std::max(scale, default_scale);
+    scale = std::max(scale, default_scale);
+    return gfx::Vector2dF(scale, scale);
   }
 
   gfx::Vector2dF transform_scales =
       gfx::ComputeTransform2dScaleComponents(transform, default_scale);
 
-  return GetPreferredRasterScale(transform_scales);
+  // TODO(crbug.com/1196414): Remove this scale cap.
+  float scale_cap = GetPreferredRasterScale(transform_scales);
+  transform_scales.SetToMin(gfx::Vector2dF(scale_cap, scale_cap));
+  return transform_scales;
+}
+
+float LayerImpl::GetIdealContentsScaleKey() const {
+  return GetPreferredRasterScale(GetIdealContentsScale());
 }
 
 float LayerImpl::GetPreferredRasterScale(
diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h
index 66c0b80..483f4b52 100644
--- a/cc/layers/layer_impl.h
+++ b/cc/layers/layer_impl.h
@@ -385,7 +385,14 @@
   // PopulateScaledSharedQuadStateQuadState() for more details.
   gfx::Rect GetScaledEnclosingRectInTargetSpace(float scale) const;
 
-  float GetIdealContentsScale() const;
+  // GetIdealContentsScale() returns the ideal 2D scale, clamped to not exceed
+  // GetPreferredRasterScale().
+  // GetIdealContentsScaleKey() returns the maximum component, a fallback to
+  // uniform scale for callers that don't support 2d scales yet.
+  // TODO(crbug.com/1196414): Remove uses of GetIdealContentsScaleKey() outside
+  // tests, and rename it to GetIdealContentsScaleKeyForTest().
+  gfx::Vector2dF GetIdealContentsScale() const;
+  float GetIdealContentsScaleKey() const;
 
   void NoteLayerPropertyChanged();
   void NoteLayerPropertyChangedFromPropertyTrees();
diff --git a/cc/layers/layer_impl_unittest.cc b/cc/layers/layer_impl_unittest.cc
index 7ef644a..825f339 100644
--- a/cc/layers/layer_impl_unittest.cc
+++ b/cc/layers/layer_impl_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "cc/layers/layer_impl.h"
 
+#include <algorithm>
+
 #include "base/stl_util.h"
 #include "cc/layers/painted_scrollbar_layer_impl.h"
 #include "cc/layers/solid_color_scrollbar_layer_impl.h"
@@ -263,7 +265,7 @@
     layer->draw_properties().screen_space_transform = transform;
 
     ASSERT_TRUE(layer->ScreenSpaceTransform().HasPerspective());
-    EXPECT_FLOAT_EQ(15.f, layer->GetIdealContentsScale());
+    EXPECT_FLOAT_EQ(15.f, layer->GetIdealContentsScaleKey());
   }
   // Ensure that we don't fall below the device scale factor.
   {
@@ -273,7 +275,7 @@
     layer->draw_properties().screen_space_transform = transform;
 
     ASSERT_TRUE(layer->ScreenSpaceTransform().HasPerspective());
-    EXPECT_FLOAT_EQ(1.f, layer->GetIdealContentsScale());
+    EXPECT_FLOAT_EQ(1.f, layer->GetIdealContentsScaleKey());
   }
   // Ensure that large scales don't end up extremely large.
   {
@@ -283,7 +285,7 @@
     layer->draw_properties().screen_space_transform = transform;
 
     ASSERT_TRUE(layer->ScreenSpaceTransform().HasPerspective());
-    EXPECT_FLOAT_EQ(127.f, layer->GetIdealContentsScale());
+    EXPECT_FLOAT_EQ(127.f, layer->GetIdealContentsScaleKey());
   }
   // Test case from crbug.com/766021.
   {
@@ -294,7 +296,7 @@
     layer->draw_properties().screen_space_transform = transform;
 
     ASSERT_TRUE(layer->ScreenSpaceTransform().HasPerspective());
-    EXPECT_FLOAT_EQ(1.f, layer->GetIdealContentsScale());
+    EXPECT_FLOAT_EQ(1.f, layer->GetIdealContentsScaleKey());
   }
 }
 
diff --git a/cc/layers/mirror_layer_impl.cc b/cc/layers/mirror_layer_impl.cc
index 2343eeb..6ef7d97e 100644
--- a/cc/layers/mirror_layer_impl.cc
+++ b/cc/layers/mirror_layer_impl.cc
@@ -40,9 +40,10 @@
   const bool contents_opaque = false;
   viz::SharedQuadState* shared_quad_state =
       render_pass->CreateAndAppendSharedQuadState();
+  // TODO(crbug.com/1196414): Support 2D scales in mirror layers.
   PopulateScaledSharedQuadStateWithContentRects(
-      shared_quad_state, mirrored_layer->GetIdealContentsScale(), content_rect,
-      content_rect, contents_opaque);
+      shared_quad_state, mirrored_layer->GetIdealContentsScaleKey(),
+      content_rect, content_rect, contents_opaque);
 
   AppendDebugBorderQuad(render_pass, content_rect, shared_quad_state,
                         append_quads_data);
@@ -77,8 +78,9 @@
 gfx::Rect MirrorLayerImpl::GetEnclosingRectInTargetSpace() const {
   const LayerImpl* mirrored_layer =
       layer_tree_impl()->LayerById(mirrored_layer_id_);
+  // TODO(crbug.com/1196414): Support 2D scales in mirror layers.
   return GetScaledEnclosingRectInTargetSpace(
-      mirrored_layer->GetIdealContentsScale());
+      mirrored_layer->GetIdealContentsScaleKey());
 }
 
 const char* MirrorLayerImpl::LayerTypeAsString() const {
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index 08b2552a..264bfb9 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -272,13 +272,17 @@
     // Validate that the tile and bounds size are always within one pixel.
     PictureLayerTiling* high_res =
         tilings_->FindTilingWithResolution(HIGH_RESOLUTION);
-    if (raster_contents_scale_ >= 1.f && high_res) {
+    if (high_res) {
       const float epsilon = 1.f;
       gfx::SizeF scaled_tiling_size(high_res->tiling_size());
-      scaled_tiling_size.Scale(1 / raster_contents_scale_);
-      DCHECK(std::abs(bounds().width() - scaled_tiling_size.width()) < epsilon);
-      DCHECK(std::abs(bounds().height() - scaled_tiling_size.height()) <
-             epsilon);
+      scaled_tiling_size.Scale(1 / raster_contents_scale_.x(),
+                               1 / raster_contents_scale_.y());
+      if (raster_contents_scale_.x() >= 1.f)
+        DCHECK(std::abs(bounds().width() - scaled_tiling_size.width()) <
+               epsilon);
+      if (raster_contents_scale_.y() >= 1.f)
+        DCHECK(std::abs(bounds().height() - scaled_tiling_size.height()) <
+               epsilon);
     }
 #endif
   }
@@ -358,7 +362,8 @@
   if (ShowDebugBorders(DebugBorderType::LAYER)) {
     for (PictureLayerTilingSet::CoverageIterator iter(
              tilings_.get(), max_contents_scale,
-             shared_quad_state->visible_quad_layer_rect, ideal_contents_scale_);
+             shared_quad_state->visible_quad_layer_rect,
+             ideal_contents_scale_key());
          iter; ++iter) {
       SkColor color;
       float width;
@@ -430,7 +435,8 @@
       raster_source_->RecordedViewport(), max_contents_scale);
   for (PictureLayerTilingSet::CoverageIterator iter(
            tilings_.get(), max_contents_scale,
-           shared_quad_state->visible_quad_layer_rect, ideal_contents_scale_);
+           shared_quad_state->visible_quad_layer_rect,
+           ideal_contents_scale_key());
        iter; ++iter) {
     gfx::Rect geometry_rect = iter.geometry_rect();
     gfx::Rect visible_geometry_rect =
@@ -464,8 +470,8 @@
           // complete. But if a tile is ideal scale, we don't want to consider
           // it incomplete and trying to replace it with a tile at a worse
           // scale.
-          if (iter->contents_scale_key() != raster_contents_scale_ &&
-              iter->contents_scale_key() != ideal_contents_scale_ &&
+          if (iter->contents_scale_key() != raster_contents_scale_key() &&
+              iter->contents_scale_key() != ideal_contents_scale_key() &&
               geometry_rect.Intersects(scaled_viewport_for_tile_priority)) {
             append_quads_data->num_incomplete_tiles++;
           }
@@ -583,8 +589,8 @@
   if (!CanHaveTilings()) {
     ideal_page_scale_ = 0.f;
     ideal_device_scale_ = 0.f;
-    ideal_contents_scale_ = 0.f;
-    ideal_source_scale_ = 0.f;
+    ideal_contents_scale_ = gfx::Vector2dF(0.f, 0.f);
+    ideal_source_scale_ = gfx::Vector2dF(0.f, 0.f);
     SanityCheckTilingState();
     return false;
   }
@@ -609,8 +615,10 @@
 
   DCHECK(raster_page_scale_);
   DCHECK(raster_device_scale_);
-  DCHECK(raster_source_scale_);
-  DCHECK(raster_contents_scale_);
+  DCHECK(raster_source_scale_.x());
+  DCHECK(raster_source_scale_.y());
+  DCHECK(raster_contents_scale_.x());
+  DCHECK(raster_contents_scale_.y());
   DCHECK(low_res_raster_contents_scale_);
 
   was_screen_space_transform_animating_ =
@@ -650,9 +658,9 @@
   // Pass |occlusion_in_content_space| for |occlusion_in_layer_space| since
   // they are the same space in picture layer, as contents scale is always 1.
   bool updated = tilings_->UpdateTilePriorities(
-      viewport_rect_for_tile_priority_in_content_space_, ideal_contents_scale_,
-      current_frame_time_in_seconds, occlusion_in_content_space,
-      can_require_tiles_for_activation);
+      viewport_rect_for_tile_priority_in_content_space_,
+      ideal_contents_scale_key(), current_frame_time_in_seconds,
+      occlusion_in_content_space, can_require_tiles_for_activation);
   DCHECK_GT(tilings_->num_tilings(), 0u);
   SanityCheckTilingState();
   return updated;
@@ -1048,7 +1056,7 @@
   gfx::Rect content_rect =
       gfx::ScaleToEnclosingRect(gfx::Rect(bounds()), dest_scale);
   PictureLayerTilingSet::CoverageIterator iter(
-      tilings_.get(), dest_scale, content_rect, ideal_contents_scale_);
+      tilings_.get(), dest_scale, content_rect, ideal_contents_scale_key());
 
   // Mask resource not ready yet.
   if (!iter || !*iter) {
@@ -1153,7 +1161,7 @@
   // ideal source scale.
   float adjusted_raster_scale = default_raster_scale_changed
                                     ? default_raster_scale
-                                    : raster_source_scale_;
+                                    : raster_source_scale_key();
 
   // We never want a raster scale larger than the default, since that uses more
   // memory but can't result it better quality (upscaling will happen in the
@@ -1162,7 +1170,7 @@
   float min_scale = MinimumContentsScale();
 
   float clamped_ideal_source_scale =
-      base::ClampToRange(ideal_source_scale_, min_scale, max_scale);
+      base::ClampToRange(ideal_source_scale_key(), min_scale, max_scale);
   while (adjusted_raster_scale < clamped_ideal_source_scale)
     adjusted_raster_scale *= 2.f;
   while (adjusted_raster_scale > 4 * clamped_ideal_source_scale)
@@ -1176,8 +1184,10 @@
 PictureLayerTiling* PictureLayerImpl::AddTiling(
     const gfx::AxisTransform2d& raster_transform) {
   DCHECK(CanHaveTilings());
-  DCHECK_GE(raster_transform.scale(), MinimumContentsScale());
-  DCHECK_LE(raster_transform.scale(), MaximumContentsScale());
+  DCHECK_GE(raster_transform.scale().x(), MinimumContentsScale());
+  DCHECK_GE(raster_transform.scale().y(), MinimumContentsScale());
+  DCHECK_LE(raster_transform.scale().x(), MaximumContentsScale());
+  DCHECK_LE(raster_transform.scale().y(), MaximumContentsScale());
   DCHECK(raster_source_->HasRecordings());
   bool tiling_can_use_lcd_text =
       can_use_lcd_text() && raster_transform.scale() == raster_contents_scale_;
@@ -1216,19 +1226,20 @@
 void PictureLayerImpl::UpdateTilingsForRasterScaleAndTranslation(
     bool has_adjusted_raster_scale) {
   PictureLayerTiling* high_res =
-      tilings_->FindTilingWithScaleKey(raster_contents_scale_);
+      tilings_->FindTilingWithScaleKey(raster_contents_scale_key());
 
   gfx::Vector2dF raster_translation;
   bool raster_translation_aligns_pixels =
       CalculateRasterTranslation(raster_translation);
   UpdateCanUseLCDText(raster_translation_aligns_pixels);
   if (high_res) {
-    bool raster_translation_is_not_ideal =
+    bool raster_transform_is_not_ideal =
+        high_res->raster_transform().scale() != raster_contents_scale_ ||
         high_res->raster_transform().translation() != raster_translation;
     bool can_use_lcd_text_changed =
         high_res->can_use_lcd_text() != can_use_lcd_text();
     bool should_recreate_high_res =
-        (raster_translation_is_not_ideal || can_use_lcd_text_changed) &&
+        (raster_transform_is_not_ideal || can_use_lcd_text_changed) &&
         CanRecreateHighResTilingForLCDTextAndRasterTranslation(*high_res);
     if (should_recreate_high_res) {
       tilings_->Remove(high_res);
@@ -1278,7 +1289,7 @@
 }
 
 bool PictureLayerImpl::ShouldAdjustRasterScale() const {
-  if (!raster_contents_scale_)
+  if (!raster_contents_scale_.x() || !raster_contents_scale_.y())
     return true;
 
   if (directly_composited_image_size_) {
@@ -1296,9 +1307,10 @@
     // noticeable quality. We'll also bump the scale back up in the case where
     // the ideal scale is increased.
     float max_scale = std::max(default_raster_scale, MinimumContentsScale());
-    if (raster_source_scale_ < std::min(ideal_source_scale_, max_scale))
+    if (raster_source_scale_key() <
+        std::min(ideal_source_scale_key(), max_scale))
       return true;
-    if (raster_source_scale_ > 4 * ideal_source_scale_)
+    if (raster_source_scale_key() > 4 * ideal_source_scale_key())
       return true;
 
     // If the default raster scale changed, that means the bounds or image size
@@ -1341,9 +1353,13 @@
   if (raster_device_scale_ != ideal_device_scale_)
     return true;
 
-  if (raster_contents_scale_ > MaximumContentsScale())
+  float max_scale = MaximumContentsScale();
+  if (raster_contents_scale_.x() > max_scale ||
+      raster_contents_scale_.y() > max_scale)
     return true;
-  if (raster_contents_scale_ < MinimumContentsScale())
+  float min_scale = MinimumContentsScale();
+  if (raster_contents_scale_.x() < min_scale ||
+      raster_contents_scale_.y() < min_scale)
     return true;
 
   // Avoid frequent raster scale changes if we have an animating transform.
@@ -1354,9 +1370,12 @@
       return true;
     // Or when the raster scale is not affected by invalid scale and is too
     // small compared to the ideal scale.
-    if (ideal_contents_scale_ >
-        raster_contents_scale_ *
-            kRatioToAdjustRasterScaleForTransformAnimation) {
+    if (ideal_contents_scale_.x() >
+            raster_contents_scale_.x() *
+                kRatioToAdjustRasterScaleForTransformAnimation ||
+        ideal_contents_scale_.y() >
+            raster_contents_scale_.y() *
+                kRatioToAdjustRasterScaleForTransformAnimation) {
       auto* property_trees = layer_tree_impl()->property_trees();
       int transform_id = transform_tree_index();
       if (property_trees->AnimationScaleCacheIsInvalid(transform_id) ||
@@ -1373,10 +1392,11 @@
 
   // Don't update will-change: transform layers if the raster contents scale is
   // bigger than the minimum scale.
-  if (HasWillChangeTransformHint() &&
-      raster_contents_scale_ >=
-          MinimumRasterContentsScaleForWillChangeTransform()) {
-    return false;
+  if (HasWillChangeTransformHint()) {
+    float min_raster_scale = MinimumRasterContentsScaleForWillChangeTransform();
+    if (raster_contents_scale_.x() >= min_raster_scale &&
+        raster_contents_scale_.y() >= min_raster_scale)
+      return false;
   }
 
   // Match the raster scale in all other cases.
@@ -1392,7 +1412,7 @@
   // We should have a high resolution tiling at raster_contents_scale, so if the
   // low res one is the same then we shouldn't try to override this tiling by
   // marking it as a low res.
-  if (raster_contents_scale_ == low_res_raster_contents_scale_)
+  if (raster_contents_scale_key() == low_res_raster_contents_scale_)
     return;
 
   PictureLayerTiling* low_res =
@@ -1414,15 +1434,17 @@
 
 void PictureLayerImpl::RecalculateRasterScales() {
   if (directly_composited_image_size_) {
+    // TODO(crbug.com/1196414): Support 2D scales in directly composited images.
     float used_raster_scale = CalculateDirectlyCompositedImageRasterScale();
     if (ShouldDirectlyCompositeImage(used_raster_scale)) {
       directly_composited_image_initial_raster_scale_ =
           GetDefaultDirectlyCompositedImageRasterScale();
-      raster_source_scale_ = used_raster_scale;
+      raster_source_scale_ =
+          gfx::Vector2dF(used_raster_scale, used_raster_scale);
       raster_page_scale_ = 1.f;
       raster_device_scale_ = 1.f;
       raster_contents_scale_ = raster_source_scale_;
-      low_res_raster_contents_scale_ = raster_contents_scale_;
+      low_res_raster_contents_scale_ = used_raster_scale;
       return;
     }
 
@@ -1432,11 +1454,11 @@
     directly_composited_image_initial_raster_scale_ = 0.f;
   }
 
-  float old_raster_contents_scale = raster_contents_scale_;
+  gfx::Vector2dF old_raster_contents_scale = raster_contents_scale_;
   float old_raster_page_scale = raster_page_scale_;
 
   // The raster scale if previous tilings should be preserved.
-  float preserved_raster_contents_scale = old_raster_contents_scale;
+  gfx::Vector2dF preserved_raster_contents_scale = old_raster_contents_scale;
 
   raster_device_scale_ = ideal_device_scale_;
   raster_page_scale_ = ideal_page_scale_;
@@ -1446,72 +1468,84 @@
   // During pinch we completely ignore the current ideal scale, and just use
   // a multiple of the previous scale.
   bool is_pinching = layer_tree_impl()->PinchGestureActive();
-  if (is_pinching && old_raster_contents_scale) {
+  if (is_pinching && !old_raster_contents_scale.IsZero()) {
     // See ShouldAdjustRasterScale:
     // - When zooming out, preemptively create new tiling at lower resolution.
     // - When zooming in, approximate ideal using multiple of kMaxScaleRatio.
     bool zooming_out = old_raster_page_scale > ideal_page_scale_;
-    float desired_contents_scale = old_raster_contents_scale;
+    float desired_contents_scale =
+        std::max(old_raster_contents_scale.x(), old_raster_contents_scale.y());
+    float ideal_scale = ideal_contents_scale_key();
     if (zooming_out) {
-      while (desired_contents_scale > ideal_contents_scale_)
+      while (desired_contents_scale > ideal_scale)
         desired_contents_scale /= kMaxScaleRatioDuringPinch;
     } else {
-      while (desired_contents_scale < ideal_contents_scale_)
+      while (desired_contents_scale < ideal_scale)
         desired_contents_scale *= kMaxScaleRatioDuringPinch;
     }
-    raster_contents_scale_ = preserved_raster_contents_scale =
-        tilings_->GetSnappedContentsScaleKey(desired_contents_scale,
-                                             kSnapToExistingTilingRatio);
+    if (const auto* snapped_to_tiling = tilings_->FindTilingWithNearestScaleKey(
+            desired_contents_scale, kSnapToExistingTilingRatio)) {
+      raster_contents_scale_ = snapped_to_tiling->raster_transform().scale();
+    } else {
+      raster_contents_scale_ = old_raster_contents_scale;
+      raster_contents_scale_.Scale(desired_contents_scale /
+                                   raster_contents_scale_key());
+    }
+    preserved_raster_contents_scale = raster_contents_scale_;
     raster_page_scale_ =
-        raster_contents_scale_ / raster_device_scale_ / raster_source_scale_;
+        std::max(raster_contents_scale_.x() / raster_source_scale_.x(),
+                 raster_contents_scale_.y() / raster_source_scale_.y()) /
+        raster_device_scale_;
   }
 
   if (draw_properties().screen_space_transform_is_animating)
     AdjustRasterScaleForTransformAnimation(preserved_raster_contents_scale);
 
   if (HasWillChangeTransformHint()) {
-    raster_contents_scale_ =
-        std::max(raster_contents_scale_,
-                 MinimumRasterContentsScaleForWillChangeTransform());
+    float min_scale = MinimumRasterContentsScaleForWillChangeTransform();
+    raster_contents_scale_.SetToMax(gfx::Vector2dF(min_scale, min_scale));
   }
 
-  raster_contents_scale_ =
-      std::max(raster_contents_scale_, MinimumContentsScale());
-  raster_contents_scale_ =
-      std::min(raster_contents_scale_, MaximumContentsScale());
-  DCHECK_GE(raster_contents_scale_, MinimumContentsScale());
-  DCHECK_LE(raster_contents_scale_, MaximumContentsScale());
+  float min_scale = MinimumContentsScale();
+  float max_scale = MaximumContentsScale();
+  raster_contents_scale_.SetToMax(gfx::Vector2dF(min_scale, min_scale));
+  raster_contents_scale_.SetToMin(gfx::Vector2dF(max_scale, max_scale));
+  DCHECK_GE(raster_contents_scale_.x(), min_scale);
+  DCHECK_GE(raster_contents_scale_.y(), min_scale);
+  DCHECK_LE(raster_contents_scale_.x(), max_scale);
+  DCHECK_LE(raster_contents_scale_.y(), max_scale);
 
   // If this layer would create zero or one tiles at this content scale,
   // don't create a low res tiling.
-  gfx::Size raster_bounds =
-      gfx::ScaleToCeiledSize(raster_source_->GetSize(), raster_contents_scale_);
+  gfx::Size raster_bounds = gfx::ScaleToCeiledSize(raster_source_->GetSize(),
+                                                   raster_contents_scale_.x(),
+                                                   raster_contents_scale_.y());
   gfx::Size tile_size = CalculateTileSize(raster_bounds);
   bool tile_covers_bounds = tile_size.width() >= raster_bounds.width() &&
                             tile_size.height() >= raster_bounds.height();
   if (tile_size.IsEmpty() || tile_covers_bounds) {
-    low_res_raster_contents_scale_ = raster_contents_scale_;
+    low_res_raster_contents_scale_ = raster_contents_scale_key();
     return;
   }
 
   float low_res_factor =
       layer_tree_impl()->settings().low_res_contents_scale_factor;
   low_res_raster_contents_scale_ =
-      std::max(raster_contents_scale_ * low_res_factor, MinimumContentsScale());
-  DCHECK_LE(low_res_raster_contents_scale_, raster_contents_scale_);
-  DCHECK_GE(low_res_raster_contents_scale_, MinimumContentsScale());
-  DCHECK_LE(low_res_raster_contents_scale_, MaximumContentsScale());
+      std::max(raster_contents_scale_key() * low_res_factor, min_scale);
+  DCHECK_LE(low_res_raster_contents_scale_, raster_contents_scale_key());
+  DCHECK_GE(low_res_raster_contents_scale_, min_scale);
+  DCHECK_LE(low_res_raster_contents_scale_, max_scale);
 }
 
 void PictureLayerImpl::AdjustRasterScaleForTransformAnimation(
-    float preserved_raster_contents_scale) {
+    const gfx::Vector2dF& preserved_raster_contents_scale) {
   DCHECK(draw_properties().screen_space_transform_is_animating);
 
+  // TODO(crbug.com/1196414): Support 2D scales in animations.
   float maximum_animation_scale =
       layer_tree_impl()->property_trees()->MaximumAnimationToScreenScale(
           transform_tree_index());
-  raster_contents_scale_ =
-      std::max(raster_contents_scale_, maximum_animation_scale);
+  float scale = std::max(raster_contents_scale_key(), maximum_animation_scale);
 
   // However we want to avoid excessive memory use. Choose a scale at which this
   // layer's rastered content is not larger than the viewport.
@@ -1532,19 +1566,19 @@
       std::min(raster_source_size.width(), max_viewport_dimension),
       std::min(raster_source_size.height(), max_viewport_dimension));
   gfx::SizeF max_visible_bounds_at_max_scale =
-      gfx::ScaleSize(max_visible_bounds, raster_contents_scale_);
+      gfx::ScaleSize(max_visible_bounds, scale);
   float maximum_area = max_visible_bounds_at_max_scale.width() *
                        max_visible_bounds_at_max_scale.height();
   // Clamp the scale to make the rastered content not larger than the viewport.
   if (UNLIKELY(maximum_area > squared_viewport_area))
-    raster_contents_scale_ /= std::sqrt(maximum_area / squared_viewport_area);
+    scale /= std::sqrt(maximum_area / squared_viewport_area);
+  raster_contents_scale_ = gfx::Vector2dF(scale, scale);
 
   if (HasWillChangeTransformHint()) {
     // If we have a will-change: transform hint, do not shrink the content
     // raster scale, otherwise we will end up throwing away larger tiles we
     // may need again.
-    raster_contents_scale_ =
-        std::max(preserved_raster_contents_scale, raster_contents_scale_);
+    raster_contents_scale_.SetToMax(preserved_raster_contents_scale);
   }
 }
 
@@ -1554,19 +1588,19 @@
   if (tilings_->num_tilings() == 0)
     return;
 
-  float min_acceptable_high_res_scale = std::min(
-      raster_contents_scale_, ideal_contents_scale_);
-  float max_acceptable_high_res_scale = std::max(
-      raster_contents_scale_, ideal_contents_scale_);
+  float min_acceptable_high_res_scale =
+      std::min(raster_contents_scale_key(), ideal_contents_scale_key());
+  float max_acceptable_high_res_scale =
+      std::max(raster_contents_scale_key(), ideal_contents_scale_key());
 
   PictureLayerImpl* twin = GetPendingOrActiveTwinLayer();
   if (twin && twin->CanHaveTilings()) {
-    min_acceptable_high_res_scale =
-        std::min({min_acceptable_high_res_scale, twin->raster_contents_scale_,
-                  twin->ideal_contents_scale_});
-    max_acceptable_high_res_scale =
-        std::max({max_acceptable_high_res_scale, twin->raster_contents_scale_,
-                  twin->ideal_contents_scale_});
+    min_acceptable_high_res_scale = std::min(
+        {min_acceptable_high_res_scale, twin->raster_contents_scale_key(),
+         twin->ideal_contents_scale_key()});
+    max_acceptable_high_res_scale = std::max(
+        {max_acceptable_high_res_scale, twin->raster_contents_scale_key(),
+         twin->ideal_contents_scale_key()});
   }
 
   PictureLayerTilingSet* twin_set = twin ? twin->tilings_.get() : nullptr;
@@ -1579,13 +1613,13 @@
     const {
   DCHECK(HasWillChangeTransformHint());
   float native_scale = ideal_device_scale_ * ideal_page_scale_;
+  float ideal_scale = ideal_contents_scale_key();
   // Clamp will-change: transform layers to be at least the native scale,
   // unless the scale is too small to avoid too many tiles using too much tile
   // memory.
-  if (ideal_contents_scale_ <
-      native_scale * kMinScaleRatioForWillChangeTransform) {
+  if (ideal_scale < native_scale * kMinScaleRatioForWillChangeTransform) {
     // Don't let the scale too small compared to the ideal scale.
-    return ideal_contents_scale_ * kMinScaleRatioForWillChangeTransform;
+    return ideal_scale * kMinScaleRatioForWillChangeTransform;
   }
   return native_scale;
 }
@@ -1621,7 +1655,9 @@
   static constexpr float kPixelErrorThreshold = 0.001f;
   static constexpr float kScaleErrorThreshold = kPixelErrorThreshold / 10000;
   auto is_raster_scale = [this](float scale) -> bool {
-    return std::abs(scale - raster_contents_scale_) <= kScaleErrorThreshold;
+    return std::abs(scale - raster_contents_scale_.x()) <=
+               kScaleErrorThreshold &&
+           std::abs(scale - raster_contents_scale_.y()) <= kScaleErrorThreshold;
   };
   if (!is_raster_scale(screen_transform.matrix().getFloat(0, 0)) ||
       !is_raster_scale(screen_transform.matrix().getFloat(1, 1)) ||
@@ -1685,8 +1721,8 @@
 void PictureLayerImpl::ResetRasterScale() {
   raster_page_scale_ = 0.f;
   raster_device_scale_ = 0.f;
-  raster_source_scale_ = 0.f;
-  raster_contents_scale_ = 0.f;
+  raster_source_scale_ = gfx::Vector2dF(0.f, 0.f);
+  raster_contents_scale_ = gfx::Vector2dF(0.f, 0.f);
   low_res_raster_contents_scale_ = 0.f;
   directly_composited_image_initial_raster_scale_ = 0.f;
 }
@@ -1772,12 +1808,14 @@
          external_page_scale_factor == 1.f ||
          layer_tree_impl()->current_page_scale_factor() == 1.f);
   ideal_page_scale_ *= external_page_scale_factor;
-  ideal_contents_scale_ *= external_page_scale_factor;
+  ideal_contents_scale_.Scale(external_page_scale_factor);
 
-  ideal_contents_scale_ = base::ClampToRange(
-      ideal_contents_scale_, min_contents_scale, kMaxIdealContentsScale);
-  ideal_source_scale_ =
-      ideal_contents_scale_ / ideal_page_scale_ / ideal_device_scale_;
+  ideal_contents_scale_.SetToMax(
+      gfx::Vector2dF(min_contents_scale, min_contents_scale));
+  ideal_contents_scale_.SetToMin(
+      gfx::Vector2dF(kMaxIdealContentsScale, kMaxIdealContentsScale));
+  ideal_source_scale_ = ideal_contents_scale_;
+  ideal_source_scale_.Scale(1 / ideal_page_scale_ / ideal_device_scale_);
 }
 
 void PictureLayerImpl::GetDebugBorderProperties(
@@ -1805,7 +1843,7 @@
 void PictureLayerImpl::AsValueInto(
     base::trace_event::TracedValue* state) const {
   LayerImpl::AsValueInto(state);
-  state->SetDouble("ideal_contents_scale", ideal_contents_scale_);
+  state->SetDouble("ideal_contents_scale", ideal_contents_scale_key());
   state->SetDouble("geometry_contents_scale", MaximumTilingContentsScale());
   state->BeginArray("tilings");
   tilings_->AsValueInto(state);
@@ -1831,7 +1869,7 @@
   state->BeginArray("coverage_tiles");
   for (PictureLayerTilingSet::CoverageIterator iter(
            tilings_.get(), MaximumTilingContentsScale(),
-           gfx::Rect(raster_source_->GetSize()), ideal_contents_scale_);
+           gfx::Rect(raster_source_->GetSize()), ideal_contents_scale_key());
        iter; ++iter) {
     state->BeginDictionary();
 
@@ -1858,16 +1896,28 @@
   state->BeginDictionary("raster_scales");
   state->SetDouble("page_scale", raster_page_scale_);
   state->SetDouble("device_scale", raster_device_scale_);
-  state->SetDouble("source_scale", raster_source_scale_);
-  state->SetDouble("contents_scale", raster_contents_scale_);
+  state->BeginArray("source_scale");
+  state->AppendDouble(raster_source_scale_.x());
+  state->AppendDouble(raster_source_scale_.y());
+  state->EndArray();
+  state->BeginArray("contents_scale");
+  state->AppendDouble(raster_contents_scale_.x());
+  state->AppendDouble(raster_contents_scale_.y());
+  state->EndArray();
   state->SetDouble("low_res_contents_scale", low_res_raster_contents_scale_);
   state->EndDictionary();
 
   state->BeginDictionary("ideal_scales");
   state->SetDouble("page_scale", ideal_page_scale_);
   state->SetDouble("device_scale", ideal_device_scale_);
-  state->SetDouble("source_scale", ideal_source_scale_);
-  state->SetDouble("contents_scale", ideal_contents_scale_);
+  state->BeginArray("source_scale");
+  state->AppendDouble(ideal_source_scale_.x());
+  state->AppendDouble(ideal_source_scale_.y());
+  state->EndArray();
+  state->BeginArray("contents_scale");
+  state->AppendDouble(ideal_contents_scale_.x());
+  state->AppendDouble(ideal_contents_scale_.y());
+  state->EndArray();
   state->EndDictionary();
 }
 
diff --git a/cc/layers/picture_layer_impl.h b/cc/layers/picture_layer_impl.h
index 67b1ea5f..f49f35c 100644
--- a/cc/layers/picture_layer_impl.h
+++ b/cc/layers/picture_layer_impl.h
@@ -7,6 +7,7 @@
 
 #include <stddef.h>
 
+#include <algorithm>
 #include <map>
 #include <memory>
 #include <string>
@@ -164,7 +165,8 @@
   void InvalidatePaintWorklets(const PaintWorkletInput::PropertyKey& key);
 
   void SetContentsScaleForTesting(float scale) {
-    ideal_contents_scale_ = raster_contents_scale_ = scale;
+    ideal_contents_scale_ = raster_contents_scale_ =
+        gfx::Vector2dF(scale, scale);
   }
 
   void AddLastAppendQuadsTilingForTesting(PictureLayerTiling* tiling) {
@@ -182,7 +184,7 @@
   bool ShouldAdjustRasterScale() const;
   void RecalculateRasterScales();
   void AdjustRasterScaleForTransformAnimation(
-      float preserved_raster_contents_scale);
+      const gfx::Vector2dF& preserved_raster_contents_scale);
   float MinimumRasterContentsScaleForWillChangeTransform() const;
   // Returns false if raster translation is not applicable.
   bool CalculateRasterTranslation(gfx::Vector2dF& raster_translation) const;
@@ -250,19 +252,32 @@
   // Device scale is from screen dpi, and it comes from device scale facter.
   float ideal_device_scale_ = 0.f;
   // Source scale comes from javascript css scale.
-  float ideal_source_scale_ = 0.f;
+  gfx::Vector2dF ideal_source_scale_;
   // Contents scale = device scale * page scale * source scale.
-  float ideal_contents_scale_ = 0.f;
+  gfx::Vector2dF ideal_contents_scale_;
 
   // Raster scales are set from ideal scales. They are scales we choose to
   // raster at. They may not match the ideal scales at times to avoid raster for
   // performance reasons.
   float raster_page_scale_ = 0.f;
   float raster_device_scale_ = 0.f;
-  float raster_source_scale_ = 0.f;
-  float raster_contents_scale_ = 0.f;
+  gfx::Vector2dF raster_source_scale_;
+  gfx::Vector2dF raster_contents_scale_;
   float low_res_raster_contents_scale_ = 0.f;
 
+  float ideal_source_scale_key() const {
+    return std::max(ideal_source_scale_.x(), ideal_source_scale_.y());
+  }
+  float ideal_contents_scale_key() const {
+    return std::max(ideal_contents_scale_.x(), ideal_contents_scale_.y());
+  }
+  float raster_source_scale_key() const {
+    return std::max(raster_source_scale_.x(), raster_source_scale_.y());
+  }
+  float raster_contents_scale_key() const {
+    return std::max(raster_contents_scale_.x(), raster_contents_scale_.y());
+  }
+
   bool is_backdrop_filter_mask_ : 1;
 
   bool was_screen_space_transform_animating_ : 1;
diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc
index dbf6b3b2..c45146a 100644
--- a/cc/layers/picture_layer_impl_unittest.cc
+++ b/cc/layers/picture_layer_impl_unittest.cc
@@ -160,7 +160,7 @@
     layer->draw_properties().screen_space_transform = scale_transform;
     layer->draw_properties().target_space_transform = scale_transform;
     layer->set_contributes_to_drawn_render_surface(true);
-    DCHECK_EQ(layer->GetIdealContentsScale(), ideal_contents_scale);
+    DCHECK_EQ(layer->GetIdealContentsScaleKey(), ideal_contents_scale);
   }
 
   void SetupDrawPropertiesAndUpdateTiles(FakePictureLayerImpl* layer,
@@ -1796,13 +1796,13 @@
   SetupTrees(pending_raster_source, active_raster_source);
   // Solid color raster source should not allow tilings at any scale.
   EXPECT_FALSE(active_layer()->CanHaveTilings());
-  EXPECT_EQ(0.f, active_layer()->ideal_contents_scale());
+  EXPECT_EQ(0.f, active_layer()->ideal_contents_scale_key());
 
   // Activate non-solid-color pending raster source makes active layer can have
   // tilings.
   ActivateTree();
   EXPECT_TRUE(active_layer()->CanHaveTilings());
-  EXPECT_GT(active_layer()->ideal_contents_scale(), 0.f);
+  EXPECT_GT(active_layer()->ideal_contents_scale_key(), 0.f);
 }
 
 TEST_F(NoLowResPictureLayerImplTest, MarkRequiredOffscreenTiles) {
@@ -2064,8 +2064,8 @@
   SetupDrawPropertiesAndUpdateTiles(active_layer(), 2.f, 1.f, 1.f);
 
   EXPECT_EQ(1.f, active_layer()->HighResTiling()->contents_scale_key());
-  EXPECT_EQ(1.f, active_layer()->raster_contents_scale());
-  EXPECT_EQ(2.f, active_layer()->ideal_contents_scale());
+  EXPECT_EQ(1.f, active_layer()->raster_contents_scale_key());
+  EXPECT_EQ(2.f, active_layer()->ideal_contents_scale_key());
 
   // Both tilings still exist.
   EXPECT_EQ(2.f, active_layer()->tilings()->tiling_at(0)->contents_scale_key());
diff --git a/cc/paint/oop_pixeltest.cc b/cc/paint/oop_pixeltest.cc
index 6398601..a7eaff6 100644
--- a/cc/paint/oop_pixeltest.cc
+++ b/cc/paint/oop_pixeltest.cc
@@ -198,13 +198,15 @@
     raster_implementation->RasterCHROMIUM(
         display_item_list.get(), &image_provider, options.content_size,
         options.full_raster_rect, options.playback_rect, options.post_translate,
-        options.post_scale, options.requires_clear, &max_op_size_limit);
+        gfx::Vector2dF(options.post_scale, options.post_scale),
+        options.requires_clear, &max_op_size_limit);
     for (const auto& list : options.additional_lists) {
       raster_implementation->RasterCHROMIUM(
           list.get(), &image_provider, options.content_size,
           options.full_raster_rect, options.playback_rect,
-          options.post_translate, options.post_scale, options.requires_clear,
-          &max_op_size_limit);
+          options.post_translate,
+          gfx::Vector2dF(options.post_scale, options.post_scale),
+          options.requires_clear, &max_op_size_limit);
     }
     raster_implementation->EndRasterCHROMIUM();
     raster_implementation->OrderingBarrierCHROMIUM();
diff --git a/cc/raster/gpu_raster_buffer_provider.cc b/cc/raster/gpu_raster_buffer_provider.cc
index 3726fd4..02293696e 100644
--- a/cc/raster/gpu_raster_buffer_provider.cc
+++ b/cc/raster/gpu_raster_buffer_provider.cc
@@ -83,8 +83,8 @@
       raster_source->background_color(), mailbox_needs_clear,
       playback_settings.msaa_sample_count, playback_settings.use_lcd_text,
       color_space, mailbox->name);
-  float recording_to_raster_scale =
-      transform.scale() / raster_source->recording_scale_factor();
+  gfx::Vector2dF recording_to_raster_scale = transform.scale();
+  recording_to_raster_scale.Scale(1 / raster_source->recording_scale_factor());
   gfx::Size content_size = raster_source->GetContentSize(transform.scale());
 
   // TODO(enne): could skip the clear on new textures, as the service side has
diff --git a/cc/raster/raster_buffer_provider_unittest.cc b/cc/raster/raster_buffer_provider_unittest.cc
index 4a281c3..0c37113 100644
--- a/cc/raster/raster_buffer_provider_unittest.cc
+++ b/cc/raster/raster_buffer_provider_unittest.cc
@@ -185,7 +185,7 @@
                       const gfx::Rect& full_raster_rect,
                       const gfx::Rect& playback_rect,
                       const gfx::Vector2dF& post_translate,
-                      GLfloat post_scale,
+                      const gfx::Vector2dF& post_scale,
                       bool requires_clear,
                       size_t* max_op_size_hint) override {}
   void EndRasterCHROMIUM() override {}
diff --git a/cc/raster/raster_source.cc b/cc/raster/raster_source.cc
index f02d0e2..2d93986 100644
--- a/cc/raster/raster_source.cc
+++ b/cc/raster/raster_source.cc
@@ -46,11 +46,12 @@
     const gfx::Rect& canvas_playback_rect) const {
   gfx::Rect outer_rect;
   gfx::Rect inner_rect;
-  float scale = raster_transform.scale() / recording_scale_factor_;
+  const gfx::Vector2dF& transform_scale = raster_transform.scale();
+  gfx::SizeF scale(transform_scale.x() / recording_scale_factor_,
+                   transform_scale.y() / recording_scale_factor_);
   if (!CalculateClearForOpaqueRasterRects(
-          raster_transform.translation(), gfx::SizeF(scale, scale),
-          content_size, canvas_bitmap_rect, canvas_playback_rect, outer_rect,
-          inner_rect))
+          raster_transform.translation(), scale, content_size,
+          canvas_bitmap_rect, canvas_playback_rect, outer_rect, inner_rect))
     return;
 
   raster_canvas->save();
@@ -103,8 +104,8 @@
   raster_canvas->clipRect(SkRect::Make(raster_bounds));
   raster_canvas->translate(raster_transform.translation().x(),
                            raster_transform.translation().y());
-  raster_canvas->scale(raster_transform.scale() / recording_scale_factor_,
-                       raster_transform.scale() / recording_scale_factor_);
+  raster_canvas->scale(raster_transform.scale().x() / recording_scale_factor_,
+                       raster_transform.scale().y() / recording_scale_factor_);
 
   if (is_partial_raster && requires_clear_) {
     // Because Skia treats painted regions as transparent by default, we don't
@@ -171,8 +172,10 @@
   return size_;
 }
 
-gfx::Size RasterSource::GetContentSize(float content_scale) const {
-  return gfx::ScaleToCeiledSize(GetSize(), content_scale);
+gfx::Size RasterSource::GetContentSize(
+    const gfx::Vector2dF& content_scale) const {
+  return gfx::ScaleToCeiledSize(GetSize(), content_scale.x(),
+                                content_scale.y());
 }
 
 bool RasterSource::IsSolidColor() const {
diff --git a/cc/raster/raster_source.h b/cc/raster/raster_source.h
index 99138ad..7d68e8f 100644
--- a/cc/raster/raster_source.h
+++ b/cc/raster/raster_source.h
@@ -80,7 +80,7 @@
   gfx::Size GetSize() const;
 
   // Returns the content size of this raster source at a particular scale.
-  gfx::Size GetContentSize(float content_scale) const;
+  gfx::Size GetContentSize(const gfx::Vector2dF& content_scale) const;
 
   // Populate the given list with all images that may overlap the given
   // rect in layer space.
diff --git a/cc/test/fake_picture_layer_impl.h b/cc/test/fake_picture_layer_impl.h
index d4a2441..9b8a448 100644
--- a/cc/test/fake_picture_layer_impl.h
+++ b/cc/test/fake_picture_layer_impl.h
@@ -68,8 +68,8 @@
   float raster_page_scale() const { return raster_page_scale_; }
   void set_raster_page_scale(float scale) { raster_page_scale_ = scale; }
 
-  float ideal_contents_scale() const { return ideal_contents_scale_; }
-  float raster_contents_scale() const { return raster_contents_scale_; }
+  using PictureLayerImpl::ideal_contents_scale_key;
+  using PictureLayerImpl::raster_contents_scale_key;
 
   PictureLayerTiling* HighResTiling() const;
   PictureLayerTiling* LowResTiling() const;
diff --git a/cc/tiles/picture_layer_tiling.cc b/cc/tiles/picture_layer_tiling.cc
index d0c83d6..793bdab 100644
--- a/cc/tiles/picture_layer_tiling.cc
+++ b/cc/tiles/picture_layer_tiling.cc
@@ -52,7 +52,8 @@
 
 #if DCHECK_IS_ON()
   gfx::SizeF scaled_source_size(gfx::ScaleSize(
-      gfx::SizeF(raster_source_->GetSize()), raster_transform.scale()));
+      gfx::SizeF(raster_source_->GetSize()), raster_transform.scale().x(),
+      raster_transform.scale().y()));
   gfx::Size floored_size = gfx::ToFlooredSize(scaled_source_size);
   bool is_width_empty =
       !floored_size.width() &&
@@ -397,13 +398,13 @@
     const gfx::Rect& coverage_rect)
     : tiling_(tiling),
       coverage_rect_(coverage_rect),
-      coverage_to_content_(tiling->raster_transform().scale() / coverage_scale,
-                           tiling->raster_transform().translation()) {
+      coverage_to_content_(PreScaleAxisTransform2d(tiling->raster_transform(),
+                                                   1 / coverage_scale)) {
   DCHECK(tiling_);
   // In order to avoid artifacts in geometry_rect scaling and clamping to ints,
   // the |coverage_scale| should always be at least as big as the tiling's
   // raster scales.
-  DCHECK_GE(coverage_scale, tiling_->raster_transform_.scale());
+  DCHECK_GE(coverage_scale, tiling_->contents_scale_key());
 
   // Clamp |coverage_rect| to the bounds of this tiling's raster source.
   coverage_rect_max_bounds_ =
@@ -606,7 +607,7 @@
   }
 
   const float content_to_screen_scale =
-      ideal_contents_scale / raster_transform_.scale();
+      ideal_contents_scale / contents_scale_key();
 
   const gfx::Rect* input_rects[] = {
       &visible_rect_in_layer_space, &skewport_in_layer_space,
@@ -985,11 +986,16 @@
   state->SetInteger("num_tiles", base::saturated_cast<int>(tiles_.size()));
   state->SetDouble("content_scale", contents_scale_key());
 
-  state->BeginArray("raster_transform");
-  state->AppendDouble(raster_transform_.scale());
+  state->BeginDictionary("raster_transform");
+  state->BeginArray("scale");
+  state->AppendDouble(raster_transform_.scale().x());
+  state->AppendDouble(raster_transform_.scale().y());
+  state->EndArray();
+  state->BeginArray("translation");
   state->AppendDouble(raster_transform_.translation().x());
   state->AppendDouble(raster_transform_.translation().y());
   state->EndArray();
+  state->EndDictionary();
 
   MathUtil::AddToTracedValue("visible_rect", current_visible_rect_, state);
   MathUtil::AddToTracedValue("skewport_rect", current_skewport_rect_, state);
diff --git a/cc/tiles/picture_layer_tiling.h b/cc/tiles/picture_layer_tiling.h
index 7f984961..f20f419 100644
--- a/cc/tiles/picture_layer_tiling.h
+++ b/cc/tiles/picture_layer_tiling.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <algorithm>
 #include <map>
 #include <memory>
 #include <unordered_map>
@@ -148,7 +149,10 @@
   // as the key for indexing and sorting. In theory we can have multiple
   // tilings with the same scale but different translation, but currently
   // we only allow tilings with unique scale for the sake of simplicity.
-  float contents_scale_key() const { return raster_transform_.scale(); }
+  float contents_scale_key() const {
+    const gfx::Vector2dF& scale = raster_transform_.scale();
+    return std::max(scale.x(), scale.y());
+  }
   const gfx::AxisTransform2d& raster_transform() const {
     return raster_transform_;
   }
diff --git a/cc/tiles/picture_layer_tiling_set.cc b/cc/tiles/picture_layer_tiling_set.cc
index 17d7437..70a35e1a 100644
--- a/cc/tiles/picture_layer_tiling_set.cc
+++ b/cc/tiles/picture_layer_tiling_set.cc
@@ -282,7 +282,8 @@
 
 #if DCHECK_IS_ON()
   for (const auto& tiling : tilings_) {
-    DCHECK_NE(tiling->contents_scale_key(), raster_transform.scale());
+    const gfx::Vector2dF& scale = raster_transform.scale();
+    DCHECK_NE(tiling->contents_scale_key(), std::max(scale.x(), scale.y()));
     DCHECK_EQ(tiling->raster_source(), raster_source.get());
   }
 #endif  // DCHECK_IS_ON()
@@ -326,6 +327,22 @@
   return iter->get();
 }
 
+PictureLayerTiling* PictureLayerTilingSet::FindTilingWithNearestScaleKey(
+    float start_scale,
+    float snap_to_existing_tiling_ratio) const {
+  PictureLayerTiling* nearest_tiling = nullptr;
+  float nearest_ratio = snap_to_existing_tiling_ratio;
+  for (const auto& tiling : tilings_) {
+    float tiling_contents_scale = tiling->contents_scale_key();
+    float ratio = LargerRatio(tiling_contents_scale, start_scale);
+    if (ratio <= nearest_ratio) {
+      nearest_tiling = tiling.get();
+      nearest_ratio = ratio;
+    }
+  }
+  return nearest_tiling;
+}
+
 void PictureLayerTilingSet::RemoveTilingsBelowScaleKey(
     float minimum_scale_key) {
   base::EraseIf(
@@ -369,28 +386,11 @@
     tiling->Reset();
 }
 
-float PictureLayerTilingSet::GetSnappedContentsScaleKey(
-    float start_scale,
-    float snap_to_existing_tiling_ratio) const {
-  // If a tiling exists within the max snapping ratio, snap to its scale.
-  float snapped_contents_scale = start_scale;
-  float snapped_ratio = snap_to_existing_tiling_ratio;
-  for (const auto& tiling : tilings_) {
-    float tiling_contents_scale = tiling->contents_scale_key();
-    float ratio = LargerRatio(tiling_contents_scale, start_scale);
-    if (ratio < snapped_ratio) {
-      snapped_contents_scale = tiling_contents_scale;
-      snapped_ratio = ratio;
-    }
-  }
-  return snapped_contents_scale;
-}
-
 float PictureLayerTilingSet::GetMaximumContentsScale() const {
   if (tilings_.empty())
     return 0.f;
   // The first tiling has the largest contents scale.
-  return tilings_[0]->raster_transform().scale();
+  return tilings_[0]->contents_scale_key();
 }
 
 bool PictureLayerTilingSet::TilingsNeedUpdate(
diff --git a/cc/tiles/picture_layer_tiling_set.h b/cc/tiles/picture_layer_tiling_set.h
index 82d9fe772..d580aa3 100644
--- a/cc/tiles/picture_layer_tiling_set.h
+++ b/cc/tiles/picture_layer_tiling_set.h
@@ -93,14 +93,15 @@
   PictureLayerTiling* FindTilingWithScaleKey(float scale_key) const;
   PictureLayerTiling* FindTilingWithResolution(TileResolution resolution) const;
 
-  void MarkAllTilingsNonIdeal();
-
   // If a tiling exists whose scale is within |snap_to_existing_tiling_ratio|
-  // ratio of |start_scale|, then return that tiling's scale. Otherwise, return
-  // |start_scale|. If multiple tilings match the criteria, return the one with
-  // the least ratio to |start_scale|.
-  float GetSnappedContentsScaleKey(float start_scale,
-                                   float snap_to_existing_tiling_ratio) const;
+  // ratio of |start_scale|, then return that tiling. Otherwise, return null.
+  // If multiple tilings match the criteria, return the one with the least ratio
+  // to |start_scale|.
+  PictureLayerTiling* FindTilingWithNearestScaleKey(
+      float start_scale,
+      float snap_to_existing_tiling_ratio) const;
+
+  void MarkAllTilingsNonIdeal();
 
   // Returns the maximum contents scale of all tilings, or 0 if no tilings
   // exist. Note that this returns the maximum of x and y scales depending on
diff --git a/cc/tiles/tile.cc b/cc/tiles/tile.cc
index 3119542..3afc048 100644
--- a/cc/tiles/tile.cc
+++ b/cc/tiles/tile.cc
@@ -55,11 +55,16 @@
       TRACE_DISABLED_BY_DEFAULT("cc.debug"), value, "cc::Tile", this);
   value->SetDouble("contents_scale", contents_scale_key());
 
-  value->BeginArray("raster_transform");
-  value->AppendDouble(raster_transform_.scale());
+  value->BeginDictionary("raster_transform");
+  value->BeginArray("scale");
+  value->AppendDouble(raster_transform_.scale().x());
+  value->AppendDouble(raster_transform_.scale().y());
+  value->EndArray();
+  value->BeginArray("translation");
   value->AppendDouble(raster_transform_.translation().x());
   value->AppendDouble(raster_transform_.translation().y());
   value->EndArray();
+  value->EndDictionary();
 
   MathUtil::AddToTracedValue("content_rect", content_rect_, value);
 
diff --git a/cc/tiles/tile.h b/cc/tiles/tile.h
index 6335d05..b2e820f 100644
--- a/cc/tiles/tile.h
+++ b/cc/tiles/tile.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <algorithm>
 #include <utility>
 #include <vector>
 
@@ -74,7 +75,10 @@
   const TileDrawInfo& draw_info() const { return draw_info_; }
   TileDrawInfo& draw_info() { return draw_info_; }
 
-  float contents_scale_key() const { return raster_transform_.scale(); }
+  float contents_scale_key() const {
+    const gfx::Vector2dF& scale = raster_transform_.scale();
+    return std::max(scale.x(), scale.y());
+  }
   const gfx::AxisTransform2d& raster_transform() const {
     return raster_transform_;
   }
diff --git a/cc/tiles/tile_manager.cc b/cc/tiles/tile_manager.cc
index 474ac90..22bc4ac 100644
--- a/cc/tiles/tile_manager.cc
+++ b/cc/tiles/tile_manager.cc
@@ -952,7 +952,7 @@
     if (image_to_frame_index)
       (*image_to_frame_index)[image.stable_id()] = frame_index;
 
-    DrawImage draw_image(*original_draw_image, tile->raster_transform().scale(),
+    DrawImage draw_image(*original_draw_image, tile->contents_scale_key(),
                          frame_index, raster_color_space, sdr_white_level);
     if (checker_image_tracker_.ShouldCheckerImage(draw_image, tree))
       checkered_images->push_back(draw_image.paint_image());
@@ -975,7 +975,7 @@
   for (const auto* original_draw_image : images_in_tile) {
     size_t frame_index = client_->GetFrameIndexForImage(
         original_draw_image->paint_image(), tree);
-    DrawImage draw_image(*original_draw_image, tile->raster_transform().scale(),
+    DrawImage draw_image(*original_draw_image, tile->contents_scale_key(),
                          frame_index, raster_color_space, sdr_white_level);
     if (checker_image_tracker_.ShouldCheckerImage(draw_image, tree)) {
       image_decode_queue->emplace_back(draw_image.paint_image(), decode_type);
diff --git a/cc/trees/draw_properties_unittest.cc b/cc/trees/draw_properties_unittest.cc
index 83e66c1..26792d1 100644
--- a/cc/trees/draw_properties_unittest.cc
+++ b/cc/trees/draw_properties_unittest.cc
@@ -3112,9 +3112,9 @@
 
   UpdateActiveTreeDrawProperties(device_scale_factor);
 
-  EXPECT_FLOAT_EQ(device_scale_factor, root->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(device_scale_factor, child->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(device_scale_factor, child2->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(device_scale_factor, root->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(device_scale_factor, child->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(device_scale_factor, child2->GetIdealContentsScaleKey());
 
   EXPECT_EQ(1u, GetRenderSurfaceList().size());
 
@@ -3223,9 +3223,9 @@
   UpdateActiveTreeDrawProperties(device_scale_factor);
 
   EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor,
-                  parent->GetIdealContentsScale());
+                  parent->GetIdealContentsScaleKey());
   EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor,
-                  perspective_surface->GetIdealContentsScale());
+                  perspective_surface->GetIdealContentsScaleKey());
   // Ideal scale is the max 2d scale component of the combined transform up to
   // the nearest render target. Here this includes the layer transform as well
   // as the device and page scale factors.
@@ -3235,7 +3235,7 @@
   gfx::Vector2dF scales =
       gfx::ComputeTransform2dScaleComponents(transform, 0.f);
   float max_2d_scale = std::max(scales.x(), scales.y());
-  EXPECT_FLOAT_EQ(max_2d_scale, scale_surface->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(max_2d_scale, scale_surface->GetIdealContentsScaleKey());
 
   // The ideal scale will draw 1:1 with its render target space along
   // the larger-scale axis.
@@ -3320,12 +3320,13 @@
   float expected_ideal_scale =
       device_scale_factor * page_scale_factor * initial_parent_scale;
   EXPECT_LT(expected_ideal_scale, 1.f);
-  EXPECT_FLOAT_EQ(expected_ideal_scale, parent->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(expected_ideal_scale, parent->GetIdealContentsScaleKey());
 
   expected_ideal_scale = device_scale_factor * page_scale_factor *
                          initial_parent_scale * initial_child_scale;
   EXPECT_LT(expected_ideal_scale, 1.f);
-  EXPECT_FLOAT_EQ(expected_ideal_scale, child_scale->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(expected_ideal_scale,
+                  child_scale->GetIdealContentsScaleKey());
 }
 
 TEST_F(DrawPropertiesScalingTest, IdealScaleForAnimatingLayer) {
@@ -3355,11 +3356,11 @@
 
   UpdateActiveTreeDrawProperties();
 
-  EXPECT_FLOAT_EQ(initial_parent_scale, parent->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(initial_parent_scale, parent->GetIdealContentsScaleKey());
   // Animating layers compute ideal scale in the same way as when
   // they are static.
   EXPECT_FLOAT_EQ(initial_child_scale * initial_parent_scale,
-                  child_scale->GetIdealContentsScale());
+                  child_scale->GetIdealContentsScaleKey());
 }
 
 TEST_F(DrawPropertiesTest, RenderSurfaceTransformsInHighDPI) {
@@ -5952,10 +5953,10 @@
 
   CommitAndActivate();
 
-  EXPECT_FLOAT_EQ(1.f, ImplOf(root)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(3.f, ImplOf(child1)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(3.f, ImplOf(mask)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(5.f, ImplOf(child2)->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(1.f, ImplOf(root)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(3.f, ImplOf(child1)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(3.f, ImplOf(mask)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(5.f, ImplOf(child2)->GetIdealContentsScaleKey());
 
   EXPECT_FLOAT_EQ(8.f, MaximumAnimationToScreenScale(ImplOf(child2)));
   EXPECT_FLOAT_EQ(3.f, MaximumAnimationToScreenScale(ImplOf(child1)));
@@ -5967,10 +5968,10 @@
   float device_scale_factor = 4.0f;
   CommitAndActivate(device_scale_factor);
 
-  EXPECT_FLOAT_EQ(4.f, ImplOf(root)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(12.f, ImplOf(child1)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(12.f, ImplOf(mask)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(20.f, ImplOf(child2)->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(4.f, ImplOf(root)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(12.f, ImplOf(child1)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(12.f, ImplOf(mask)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(20.f, ImplOf(child2)->GetIdealContentsScaleKey());
 
   EXPECT_FLOAT_EQ(32.f, MaximumAnimationToScreenScale(ImplOf(child2)));
   EXPECT_FLOAT_EQ(12.f, MaximumAnimationToScreenScale(ImplOf(child1)));
@@ -6021,10 +6022,10 @@
 
   CommitAndActivate();
 
-  EXPECT_FLOAT_EQ(1.f, ImplOf(root)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(1.f, ImplOf(page_scale)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(3.f, ImplOf(child1)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(5.f, ImplOf(child2)->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(1.f, ImplOf(root)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(1.f, ImplOf(page_scale)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(3.f, ImplOf(child1)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(5.f, ImplOf(child2)->GetIdealContentsScaleKey());
 
   EXPECT_FLOAT_EQ(8.f, MaximumAnimationToScreenScale(ImplOf(child2)));
   EXPECT_FLOAT_EQ(3.f, MaximumAnimationToScreenScale(ImplOf(child1)));
@@ -6038,10 +6039,10 @@
   host()->SetPageScaleFactorAndLimits(3.f, 3.f, 3.f);
   CommitAndActivate();
 
-  EXPECT_FLOAT_EQ(1.f, ImplOf(root)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(3.f, ImplOf(page_scale)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(9.f, ImplOf(child1)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(15.f, ImplOf(child2)->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(1.f, ImplOf(root)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(3.f, ImplOf(page_scale)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(9.f, ImplOf(child1)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(15.f, ImplOf(child2)->GetIdealContentsScaleKey());
 
   EXPECT_FLOAT_EQ(24.f, MaximumAnimationToScreenScale(ImplOf(child2)));
   EXPECT_FLOAT_EQ(9.f, MaximumAnimationToScreenScale(ImplOf(child1)));
@@ -6054,10 +6055,10 @@
   device_scale_factor = 4.0f;
   CommitAndActivate(device_scale_factor);
 
-  EXPECT_FLOAT_EQ(4.f, ImplOf(root)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(12.f, ImplOf(page_scale)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(36.f, ImplOf(child1)->GetIdealContentsScale());
-  EXPECT_FLOAT_EQ(60.f, ImplOf(child2)->GetIdealContentsScale());
+  EXPECT_FLOAT_EQ(4.f, ImplOf(root)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(12.f, ImplOf(page_scale)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(36.f, ImplOf(child1)->GetIdealContentsScaleKey());
+  EXPECT_FLOAT_EQ(60.f, ImplOf(child2)->GetIdealContentsScaleKey());
 
   EXPECT_FLOAT_EQ(96.f, MaximumAnimationToScreenScale(ImplOf(child2)));
   EXPECT_FLOAT_EQ(36.f, MaximumAnimationToScreenScale(ImplOf(child1)));
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index 3075f4e..cd57eca 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -1179,18 +1179,19 @@
                                           const gfx::Rect& full_raster_rect,
                                           const gfx::Rect& playback_rect,
                                           const gfx::Vector2dF& post_translate,
-                                          GLfloat post_scale,
+                                          const gfx::Vector2dF& post_scale,
                                           bool requires_clear,
                                           size_t* max_op_size_hint) {
   TRACE_EVENT1("gpu", "RasterImplementation::RasterCHROMIUM",
                "raster_chromium_id", ++raster_chromium_id_);
   DCHECK(max_op_size_hint);
 
-  if (std::abs(post_scale) < std::numeric_limits<float>::epsilon())
+  if (std::abs(post_scale.x()) < std::numeric_limits<float>::epsilon() ||
+      std::abs(post_scale.y()) < std::numeric_limits<float>::epsilon())
     return;
 
-  gfx::Rect query_rect =
-      gfx::ScaleToEnclosingRect(playback_rect, 1.f / post_scale);
+  gfx::Rect query_rect = gfx::ScaleToEnclosingRect(
+      playback_rect, 1.f / post_scale.x(), 1.f / post_scale.y());
   list->rtree_.Search(query_rect, &temp_raster_offsets_);
   // We can early out if we have nothing to draw and we don't need a clear. Note
   // that if there is nothing to draw, but a clear is required, then those
@@ -1209,7 +1210,7 @@
   preamble.full_raster_rect = full_raster_rect;
   preamble.playback_rect = playback_rect;
   preamble.post_translation = post_translate;
-  preamble.post_scale = gfx::SizeF(post_scale, post_scale);
+  preamble.post_scale = gfx::SizeF(post_scale.x(), post_scale.y());
   preamble.requires_clear = requires_clear;
   preamble.background_color = raster_properties_->background_color;
 
diff --git a/gpu/command_buffer/client/raster_implementation.h b/gpu/command_buffer/client/raster_implementation.h
index 71ee88e..99b1cd4c 100644
--- a/gpu/command_buffer/client/raster_implementation.h
+++ b/gpu/command_buffer/client/raster_implementation.h
@@ -149,7 +149,7 @@
                       const gfx::Rect& full_raster_rect,
                       const gfx::Rect& playback_rect,
                       const gfx::Vector2dF& post_translate,
-                      GLfloat post_scale,
+                      const gfx::Vector2dF& post_scale,
                       bool requires_clear,
                       size_t* max_op_size_hint) override;
   SyncToken ScheduleImageDecode(base::span<const uint8_t> encoded_data,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc
index 43abd04..b2bd85d 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles.cc
@@ -224,7 +224,7 @@
     const gfx::Rect& full_raster_rect,
     const gfx::Rect& playback_rect,
     const gfx::Vector2dF& post_translate,
-    GLfloat post_scale,
+    const gfx::Vector2dF& post_scale,
     bool requires_clear,
     size_t* max_op_size_hint) {
   NOTREACHED();
diff --git a/gpu/command_buffer/client/raster_implementation_gles.h b/gpu/command_buffer/client/raster_implementation_gles.h
index e98a837..d410a9c 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.h
+++ b/gpu/command_buffer/client/raster_implementation_gles.h
@@ -97,7 +97,7 @@
                       const gfx::Rect& full_raster_rect,
                       const gfx::Rect& playback_rect,
                       const gfx::Vector2dF& post_translate,
-                      GLfloat post_scale,
+                      const gfx::Vector2dF& post_scale,
                       bool requires_clear,
                       size_t* max_op_size_hint) override;
   void EndRasterCHROMIUM() override;
diff --git a/gpu/command_buffer/client/raster_interface.h b/gpu/command_buffer/client/raster_interface.h
index 3e19a08..d3b5bd3 100644
--- a/gpu/command_buffer/client/raster_interface.h
+++ b/gpu/command_buffer/client/raster_interface.h
@@ -89,7 +89,7 @@
                               const gfx::Rect& full_raster_rect,
                               const gfx::Rect& playback_rect,
                               const gfx::Vector2dF& post_translate,
-                              GLfloat post_scale,
+                              const gfx::Vector2dF& post_scale,
                               bool requires_clear,
                               size_t* max_op_size_hint) = 0;
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index 168785e7..908d5dd 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -531,6 +531,7 @@
     gfx::Rect full_raster_rect(Size().Width(), Size().Height());
     gfx::Rect playback_rect(Size().Width(), Size().Height());
     gfx::Vector2dF post_translate(0.f, 0.f);
+    gfx::Vector2dF post_scale(1.f, 1.f);
 
     const bool needs_clear = !is_cleared_;
     is_cleared_ = true;
@@ -542,7 +543,7 @@
 
     ri->RasterCHROMIUM(list.get(), GetOrCreateCanvasImageProvider(), size,
                        full_raster_rect, playback_rect, post_translate,
-                       1.f /* post_scale */, false /* requires_clear */,
+                       post_scale, false /* requires_clear */,
                        &max_op_size_hint);
 
     ri->EndRasterCHROMIUM();
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/mask-with-filter-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/mask-with-filter-expected.png
deleted file mode 100644
index bcbe48c..0000000
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/compositing/overflow/mask-with-filter-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
index 1ef700d2..4feb5c1 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/frames/frame-set-scaling-skew-expected.png b/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/frames/frame-set-scaling-skew-expected.png
index 6abecce..9be2751 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/frames/frame-set-scaling-skew-expected.png
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/fast/frames/frame-set-scaling-skew-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/compositing/overflow/scaled-mask-expected.png b/third_party/blink/web_tests/platform/linux/compositing/overflow/scaled-mask-expected.png
index 7f9373f..60d5698 100644
--- a/third_party/blink/web_tests/platform/linux/compositing/overflow/scaled-mask-expected.png
+++ b/third_party/blink/web_tests/platform/linux/compositing/overflow/scaled-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/compositing/transform-3d-scales-different-x-y-expected.png b/third_party/blink/web_tests/platform/linux/compositing/transform-3d-scales-different-x-y-expected.png
index daa94e8d..3459b94 100644
--- a/third_party/blink/web_tests/platform/linux/compositing/transform-3d-scales-different-x-y-expected.png
+++ b/third_party/blink/web_tests/platform/linux/compositing/transform-3d-scales-different-x-y-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png b/third_party/blink/web_tests/platform/linux/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
index 9966ced..f27b630 100644
--- a/third_party/blink/web_tests/platform/linux/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
+++ b/third_party/blink/web_tests/platform/linux/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scaled-mask-expected.png b/third_party/blink/web_tests/platform/linux/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scaled-mask-expected.png
deleted file mode 100644
index 7f9373f..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scaled-mask-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/compositing/overflow/mask-with-filter-expected.png b/third_party/blink/web_tests/platform/mac/compositing/overflow/mask-with-filter-expected.png
index 571c514..6259b6a 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/overflow/mask-with-filter-expected.png
+++ b/third_party/blink/web_tests/platform/mac/compositing/overflow/mask-with-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/compositing/overflow/scaled-mask-expected.png b/third_party/blink/web_tests/platform/mac/compositing/overflow/scaled-mask-expected.png
index 66bd3db..4556fc88 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/overflow/scaled-mask-expected.png
+++ b/third_party/blink/web_tests/platform/mac/compositing/overflow/scaled-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/compositing/transform-3d-scales-different-x-y-expected.png b/third_party/blink/web_tests/platform/mac/compositing/transform-3d-scales-different-x-y-expected.png
index a33fadfef..cdcba6e2 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/transform-3d-scales-different-x-y-expected.png
+++ b/third_party/blink/web_tests/platform/mac/compositing/transform-3d-scales-different-x-y-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png b/third_party/blink/web_tests/platform/mac/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
index 2f79827..4770032 100644
--- a/third_party/blink/web_tests/platform/mac/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
+++ b/third_party/blink/web_tests/platform/mac/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/mask-with-filter-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/mask-with-filter-expected.png
index bcbe48c..61289a3 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/mask-with-filter-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/mask-with-filter-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/scaled-mask-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/scaled-mask-expected.png
index f33f692..335b6e2 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/scaled-mask-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/scaled-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/transform-3d-scales-different-x-y-expected.png b/third_party/blink/web_tests/platform/win/compositing/transform-3d-scales-different-x-y-expected.png
index e606549..64f3206 100644
--- a/third_party/blink/web_tests/platform/win/compositing/transform-3d-scales-different-x-y-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/transform-3d-scales-different-x-y-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png b/third_party/blink/web_tests/platform/win/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
index fe1ec8b..d30ee8d 100644
--- a/third_party/blink/web_tests/platform/win/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/blending/background-blend-mode-overlapping-accelerated-elements-expected.png
Binary files differ
diff --git a/ui/gfx/geometry/axis_transform2d.cc b/ui/gfx/geometry/axis_transform2d.cc
index 5b7d86a4..fb2bb42 100644
--- a/ui/gfx/geometry/axis_transform2d.cc
+++ b/ui/gfx/geometry/axis_transform2d.cc
@@ -9,7 +9,7 @@
 namespace gfx {
 
 std::string AxisTransform2d::ToString() const {
-  return base::StringPrintf("[%f, %s]", scale_,
+  return base::StringPrintf("[%s, %s]", scale_.ToString().c_str(),
                             translation_.ToString().c_str());
 }
 
diff --git a/ui/gfx/geometry/axis_transform2d.h b/ui/gfx/geometry/axis_transform2d.h
index 57b45ac..fd7b153 100644
--- a/ui/gfx/geometry/axis_transform2d.h
+++ b/ui/gfx/geometry/axis_transform2d.h
@@ -5,6 +5,7 @@
 #ifndef UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
 #define UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
 
+#include "base/check_op.h"
 #include "ui/gfx/geometry/geometry_export.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/vector2d_f.h"
@@ -20,6 +21,9 @@
  public:
   constexpr AxisTransform2d() = default;
   constexpr AxisTransform2d(float scale, const Vector2dF& translation)
+      : scale_(scale, scale), translation_(translation) {}
+  constexpr AxisTransform2d(const Vector2dF& scale,
+                            const Vector2dF& translation)
       : scale_(scale), translation_(translation) {}
 
   bool operator==(const AxisTransform2d& other) const {
@@ -29,13 +33,13 @@
     return !(*this == other);
   }
 
-  void PreScale(float scale) { scale_ *= scale; }
-  void PostScale(float scale) {
-    scale_ *= scale;
-    translation_.Scale(scale);
+  void PreScale(const Vector2dF& scale) { scale_.Scale(scale.x(), scale.y()); }
+  void PostScale(const Vector2dF& scale) {
+    scale_.Scale(scale.x(), scale.y());
+    translation_.Scale(scale.x(), scale.y());
   }
   void PreTranslate(const Vector2dF& translation) {
-    translation_ += ScaleVector2d(translation, scale_);
+    translation_ += ScaleVector2d(translation, scale_.x(), scale_.y());
   }
   void PostTranslate(const Vector2dF& translation) {
     translation_ += translation;
@@ -51,28 +55,31 @@
   }
 
   void Invert() {
-    DCHECK(scale_);
-    scale_ = 1.f / scale_;
-    translation_.Scale(-scale_);
+    DCHECK(scale_.x());
+    DCHECK(scale_.y());
+    scale_ = Vector2dF(1.f / scale_.x(), 1.f / scale_.y());
+    translation_.Scale(-scale_.x(), -scale_.y());
   }
 
   PointF MapPoint(const PointF& p) const {
-    return ScalePoint(p, scale_) + translation_;
+    return ScalePoint(p, scale_.x(), scale_.y()) + translation_;
   }
   PointF InverseMapPoint(const PointF& p) const {
-    return ScalePoint(p - translation_, 1.f / scale_);
+    return ScalePoint(p - translation_, 1.f / scale_.x(), 1.f / scale_.y());
   }
 
   RectF MapRect(const RectF& r) const {
-    DCHECK(scale_ >= 0.f);
-    return ScaleRect(r, scale_) + translation_;
+    DCHECK_GE(scale_.x(), 0.f);
+    DCHECK_GE(scale_.y(), 0.f);
+    return ScaleRect(r, scale_.x(), scale_.y()) + translation_;
   }
   RectF InverseMapRect(const RectF& r) const {
-    DCHECK(scale_ > 0.f);
-    return ScaleRect(r - translation_, 1.f / scale_);
+    DCHECK_GT(scale_.x(), 0.f);
+    DCHECK_GT(scale_.y(), 0.f);
+    return ScaleRect(r - translation_, 1.f / scale_.x(), 1.f / scale_.y());
   }
 
-  float scale() const { return scale_; }
+  const Vector2dF& scale() const { return scale_; }
   const Vector2dF& translation() const { return translation_; }
 
   std::string ToString() const;
@@ -80,21 +87,21 @@
  private:
   // Scale is applied before translation, i.e.
   // this->Transform(p) == scale_ * p + translation_
-  float scale_ = 1.f;
+  Vector2dF scale_{1.f, 1.f};
   Vector2dF translation_;
 };
 
 inline AxisTransform2d PreScaleAxisTransform2d(const AxisTransform2d& t,
                                                float scale) {
   AxisTransform2d result(t);
-  result.PreScale(scale);
+  result.PreScale(Vector2dF(scale, scale));
   return result;
 }
 
 inline AxisTransform2d PostScaleAxisTransform2d(const AxisTransform2d& t,
                                                 float scale) {
   AxisTransform2d result(t);
-  result.PostScale(scale);
+  result.PostScale(Vector2dF(scale, scale));
   return result;
 }
 
diff --git a/ui/gfx/geometry/axis_transform2d_unittest.cc b/ui/gfx/geometry/axis_transform2d_unittest.cc
index b132c69..6bee3da 100644
--- a/ui/gfx/geometry/axis_transform2d_unittest.cc
+++ b/ui/gfx/geometry/axis_transform2d_unittest.cc
@@ -27,7 +27,7 @@
     AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
     EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)),
               PreScaleAxisTransform2d(t, 1.25));
-    t.PreScale(1.25);
+    t.PreScale(Vector2dF(1.25f, 1.25f));
     EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)), t);
   }
 
@@ -35,7 +35,7 @@
     AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
     EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)),
               PostScaleAxisTransform2d(t, 1.25));
-    t.PostScale(1.25);
+    t.PostScale(Vector2dF(1.25f, 1.25f));
     EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)), t);
   }
 }
diff --git a/ui/gfx/test/gfx_util.cc b/ui/gfx/test/gfx_util.cc
index 474a6ca..17e6869 100644
--- a/ui/gfx/test/gfx_util.cc
+++ b/ui/gfx/test/gfx_util.cc
@@ -53,7 +53,8 @@
     const char* rhs_expr,
     const AxisTransform2d& lhs,
     const AxisTransform2d& rhs) {
-  if (FloatAlmostEqual(lhs.scale(), rhs.scale()) &&
+  if (FloatAlmostEqual(lhs.scale().x(), rhs.scale().x()) &&
+      FloatAlmostEqual(lhs.scale().y(), rhs.scale().y()) &&
       FloatAlmostEqual(lhs.translation().x(), rhs.translation().x()) &&
       FloatAlmostEqual(lhs.translation().y(), rhs.translation().y())) {
     return ::testing::AssertionSuccess();