[go: nahoru, domu]

cc: Compute |visible_rect| for tiled mask layers to limit raster

For single tile mask layers |visible_rect| is only used as bool to
check for non empty. For tiled mask layers correct |visible_rect| is
needed to limit the amount of resource we generated for raster the
tiled masks. This bug was discovered when we have a tiled mask layer
with big scale and we try to create tiles to covered enlarged space
and eventually OOM.

Compute |visible_rect| correctly results in breaking the assumption
that tiled mask layer's |to_target| is only a scale. This changes the
computation in |RenderSurfaceImpl| of how to convert tile quads into
tiled mask render pass quads.

This CL is aimed to fix release blocking bug and thus has limited
scoped. There are two potential follow-ups: check for scale of tiled
mask layer, and adjust render surface rect.

For scale check: ideally tiled mask layer should be in the same
space as the render surface, this should simplify computation when
converting from tiled quads to render pass quads.

For render surface rect: it does not make sense for render surface to
have different size than the mask layer's visible rect. Right now
render surfaces are unclipped and bounded by maxium texture size, while
tiled mask visible size is clipped. To make these two sizes match there
needs more investigation into whether unit tests still make sense, and
should be in another CL.


R=danakj

Bug: 820727
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I62c7c992d99088c1f8cdbe4ffbac520b0a7d1a53
Reviewed-on: https://chromium-review.googlesource.com/1037629
Commit-Queue: weiliangc <weiliangc@chromium.org>
Reviewed-by: danakj <danakj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557985}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 26c8c56..2fe4917 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -678,6 +678,7 @@
     "trees/layer_tree_host_unittest_context.cc",
     "trees/layer_tree_host_unittest_copyrequest.cc",
     "trees/layer_tree_host_unittest_damage.cc",
+    "trees/layer_tree_host_unittest_masks.cc",
     "trees/layer_tree_host_unittest_occlusion.cc",
     "trees/layer_tree_host_unittest_picture.cc",
     "trees/layer_tree_host_unittest_proxy.cc",
diff --git a/cc/layers/picture_layer_impl.h b/cc/layers/picture_layer_impl.h
index d034810..6f85085 100644
--- a/cc/layers/picture_layer_impl.h
+++ b/cc/layers/picture_layer_impl.h
@@ -163,11 +163,20 @@
   scoped_refptr<RasterSource> raster_source_;
   Region invalidation_;
 
+  // Ideal scales are calcuated from the transforms applied to the layer. They
+  // represent the best known scale from the layer to the final output.
+  // Page scale is from user pinch/zoom.
   float ideal_page_scale_;
+  // Device scale is from screen dpi, and it comes from device scale facter.
   float ideal_device_scale_;
+  // Source scale comes from javascript css scale.
   float ideal_source_scale_;
+  // Contents scale = device scale * page scale * source scale.
   float 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_;
   float raster_device_scale_;
   float raster_source_scale_;
diff --git a/cc/layers/render_surface_impl.cc b/cc/layers/render_surface_impl.cc
index c0614fc..02caf7c 100644
--- a/cc/layers/render_surface_impl.cc
+++ b/cc/layers/render_surface_impl.cc
@@ -26,6 +26,7 @@
 #include "components/viz/common/quads/render_pass_draw_quad.h"
 #include "components/viz/common/quads/shared_quad_state.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
+#include "components/viz/common/quads/tile_draw_quad.h"
 #include "third_party/skia/include/core/SkImageFilter.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/transform.h"
@@ -267,8 +268,8 @@
   // use accumulated content rect, and then try to clip it.
   gfx::Rect surface_content_rect = CalculateClippedAccumulatedContentRect();
 
-  // The RenderSurfaceImpl backing texture cannot exceed the maximum
-  // supported texture size.
+  // The RenderSurfaceImpl backing texture cannot exceed the maximum supported
+  // texture size.
   surface_content_rect.set_width(
       std::min(surface_content_rect.width(), max_texture_size));
   surface_content_rect.set_height(
@@ -384,9 +385,9 @@
 void RenderSurfaceImpl::AppendQuads(DrawMode draw_mode,
                                     viz::RenderPass* render_pass,
                                     AppendQuadsData* append_quads_data) {
-  gfx::Rect visible_layer_rect =
+  gfx::Rect unoccluded_content_rect =
       occlusion_in_content_space().GetUnoccludedContentRect(content_rect());
-  if (visible_layer_rect.IsEmpty())
+  if (unoccluded_content_rect.IsEmpty())
     return;
 
   const PropertyTrees* property_trees = layer_tree_impl_->property_trees();
@@ -406,7 +407,7 @@
     auto* debug_border_quad =
         render_pass->CreateAndAppendDrawQuad<viz::DebugBorderDrawQuad>();
     debug_border_quad->SetNew(shared_quad_state, content_rect(),
-                              visible_layer_rect, GetDebugBorderColor(),
+                              unoccluded_content_rect, GetDebugBorderColor(),
                               GetDebugBorderWidth());
   }
 
@@ -429,7 +430,7 @@
                  "mask_layer_gpu_memory_usage",
                  mask_layer->GPUMemoryUsageInBytes());
     if (mask_layer->mask_type() == Layer::LayerMaskType::MULTI_TEXTURE_MASK) {
-      TileMaskLayer(render_pass, shared_quad_state, visible_layer_rect);
+      TileMaskLayer(render_pass, shared_quad_state, unoccluded_content_rect);
       return;
     }
     gfx::SizeF mask_uv_size;
@@ -448,21 +449,25 @@
 
   gfx::RectF tex_coord_rect(gfx::Rect(content_rect().size()));
   auto* quad = render_pass->CreateAndAppendDrawQuad<viz::RenderPassDrawQuad>();
-  quad->SetNew(shared_quad_state, content_rect(), visible_layer_rect, id(),
+  quad->SetNew(shared_quad_state, content_rect(), unoccluded_content_rect, id(),
                mask_resource_id, mask_uv_rect, mask_texture_size,
                surface_contents_scale, FiltersOrigin(), tex_coord_rect,
                !layer_tree_impl_->settings().enable_edge_anti_aliasing);
 }
 
-void RenderSurfaceImpl::TileMaskLayer(viz::RenderPass* render_pass,
-                                      viz::SharedQuadState* shared_quad_state,
-                                      const gfx::Rect& visible_layer_rect) {
+void RenderSurfaceImpl::TileMaskLayer(
+    viz::RenderPass* render_pass,
+    viz::SharedQuadState* shared_quad_state,
+    const gfx::Rect& unoccluded_content_rect) {
   DCHECK(MaskLayer());
   DCHECK(Filters().IsEmpty());
 
   LayerImpl* mask_layer = MaskLayer();
   gfx::Vector2dF owning_layer_to_surface_contents_scale =
       OwningEffectNode()->surface_contents_scale;
+
+  // Use the picture layer's AppendQuads logic to generate TileDrawQuads. These
+  // DrawQuads are used to generate tiled RenderPassDrawQuad for the mask.
   std::unique_ptr<viz::RenderPass> temp_render_pass = viz::RenderPass::Create();
   AppendQuadsData temp_append_quads_data;
   mask_layer->AppendQuads(temp_render_pass.get(), &temp_append_quads_data);
@@ -470,92 +475,158 @@
   auto* temp_quad = temp_render_pass->quad_list.front();
   if (!temp_quad)
     return;
-  gfx::Transform mask_quad_to_surface_contents =
+
+  // There are two spaces we are dealing with here:
+  // 1. quad space: This is the space where the draw quads generated by the
+  // PictureLayerImpl's logic are in. In other words, this is the space where
+  // the |temp_quad|'s rect is in.
+  // 2. surface space: This is the contents space of |this| render surface.
+  // Since |mask_layer|'s target is the render surface it's masking, the surface
+  // space is also the target space for the quads generated by
+  // PictureLayerImpl's logic.
+
+  gfx::Transform quad_space_to_surface_space_transform =
       temp_quad->shared_quad_state->quad_to_target_transform;
-  // Draw transform of a mask layer should be a 2d scale.
-  DCHECK(mask_quad_to_surface_contents.IsScale2d());
-  gfx::Vector2dF mask_quad_to_surface_contents_scale =
-      mask_quad_to_surface_contents.Scale2d();
-  shared_quad_state->quad_to_target_transform.matrix().preScale(
-      mask_quad_to_surface_contents_scale.x(),
-      mask_quad_to_surface_contents_scale.y(), 1.f);
-  shared_quad_state->quad_layer_rect =
-      gfx::ScaleToEnclosingRect(shared_quad_state->quad_layer_rect,
-                                1.f / mask_quad_to_surface_contents_scale.x(),
-                                1.f / mask_quad_to_surface_contents_scale.y());
+  // This transform should be a 2d scale + offset, so would be invertible.
+  gfx::Transform surface_space_to_quad_space_transform;
+  bool invertible = quad_space_to_surface_space_transform.GetInverse(
+      &surface_space_to_quad_space_transform);
+  DCHECK(invertible) << "RenderSurfaceImpl::TileMaskLayer created quads with "
+                        "non-invertible transform.";
+
+  // While converting from the TileDrawQuads to RenderPassDrawQuads, we keep the
+  // quad rects in the same space, and modify every other rect that is not in
+  // quad space accordingly.
+
+  // The |shared_quad_state| being passed in is generated with |this| render
+  // surface's draw properties. It holds a transform from the surface contents
+  // space to the surface target space. We want to change the origin space to
+  // match the |mask_layer|'s quad space, so we must include the transform from
+  // the quad space to the surface contents space. Then the transform is from
+  // the |mask_layer|'s quad space to our target space.
+  shared_quad_state->quad_to_target_transform.PreconcatTransform(
+      quad_space_to_surface_space_transform);
+
+  // Next, we need to modify the rects on |shared_quad_state| that are in
+  // surface's "quad space" (surface space) to quad space.
+  shared_quad_state->quad_layer_rect = MathUtil::ProjectEnclosingClippedRect(
+      surface_space_to_quad_space_transform,
+      shared_quad_state->quad_layer_rect);
   shared_quad_state->visible_quad_layer_rect =
-      gfx::ScaleToEnclosingRect(shared_quad_state->visible_quad_layer_rect,
-                                1.f / mask_quad_to_surface_contents_scale.x(),
-                                1.f / mask_quad_to_surface_contents_scale.y());
-  gfx::Rect content_rect_in_coverage_space = gfx::ScaleToEnclosingRect(
-      content_rect(), 1.f / mask_quad_to_surface_contents_scale.x(),
-      1.f / mask_quad_to_surface_contents_scale.y());
-  gfx::Rect visible_layer_rect_in_coverage_space = gfx::ScaleToEnclosingRect(
-      visible_layer_rect, 1.f / mask_quad_to_surface_contents_scale.x(),
-      1.f / mask_quad_to_surface_contents_scale.y());
+      MathUtil::ProjectEnclosingClippedRect(
+          surface_space_to_quad_space_transform,
+          shared_quad_state->visible_quad_layer_rect);
 
+  // The |shared_quad_state|'s |quad_layer_rect| and |visible_quad_layer_rect|
+  // is set from content_rect(). content_rect() defines the size of the source
+  // texture to be masked. PictureLayerImpl's generated |quad_layer_rect| and
+  // |visible_quad_layer_rect| is from the mask layer's |bounds| and
+  // |visible_layer_rect|. These rect defines the size of the mask texture. The
+  // intersection of the two rects is the rect we can draw.
+  shared_quad_state->quad_layer_rect.Intersect(
+      temp_quad->shared_quad_state->quad_layer_rect);
+  shared_quad_state->visible_quad_layer_rect.Intersect(
+      temp_quad->shared_quad_state->visible_quad_layer_rect);
+
+  // Cache content_rect() and |unoccluded_content_rect| in quad space.
+  gfx::Rect content_rect_in_quad_space = MathUtil::MapEnclosingClippedRect(
+      surface_space_to_quad_space_transform, content_rect());
+  gfx::Rect unoccluded_content_rect_in_quad_space =
+      MathUtil::MapEnclosingClippedRect(surface_space_to_quad_space_transform,
+                                        unoccluded_content_rect);
+
+  // Generate RenderPassDrawQuads based on the temporary quads created by
+  // |mask_layer|.
   for (auto* temp_quad : temp_render_pass->quad_list) {
-    gfx::Rect quad_rect = temp_quad->rect;
-    if (!quad_rect.Intersects(content_rect_in_coverage_space))
+    gfx::Rect temp_quad_rect = temp_quad->rect;
+    // If the |temp_quad_rect| is entirely outside render surface's
+    // content_rect(), ignore the quad.
+    if (!temp_quad_rect.Intersects(content_rect_in_quad_space))
       continue;
 
-    gfx::Rect render_quad_rect =
-        gfx::IntersectRects(quad_rect, content_rect_in_coverage_space);
-    gfx::Rect quad_visible_rect_in_coverage_space = gfx::IntersectRects(
-        render_quad_rect, visible_layer_rect_in_coverage_space);
-    if (quad_visible_rect_in_coverage_space.IsEmpty())
+    // We only care about the quads that are inside the content_rect().
+    gfx::Rect quad_rect =
+        gfx::IntersectRects(temp_quad_rect, content_rect_in_quad_space);
+
+    gfx::Rect visible_quad_rect =
+        gfx::IntersectRects(quad_rect, unoccluded_content_rect_in_quad_space);
+    if (visible_quad_rect.IsEmpty())
       continue;
 
-    gfx::RectF quad_rect_in_surface_contents_space = gfx::ScaleRect(
-        gfx::RectF(render_quad_rect), mask_quad_to_surface_contents_scale.x(),
-        mask_quad_to_surface_contents_scale.y());
-    gfx::RectF quad_rect_in_non_normalized_texture_space =
-        quad_rect_in_surface_contents_space;
-    quad_rect_in_non_normalized_texture_space.Offset(
-        -content_rect().OffsetFromOrigin());
+    // |tex_coord_rect| is non-normalized sub-rect of the render surface's
+    // texture that is being masked. Its origin is (0,0) and it is in surface
+    // space. For example the |tex_coord_rect| for the entire texture would be
+    // (0,0 content_rect.width X content_rect.height).
+
+    // In order to calculate the |tex_coord_rect|, we calculate what quad's rect
+    // would be masking in the surface contents space, then remove the offset.
+    gfx::RectF tex_coord_rect = MathUtil::MapClippedRect(
+        quad_space_to_surface_space_transform, gfx::RectF(quad_rect));
+    tex_coord_rect.Offset(-content_rect().OffsetFromOrigin());
 
     switch (temp_quad->material) {
       case viz::DrawQuad::TILED_CONTENT: {
         DCHECK_EQ(1U, temp_quad->resources.count);
-        gfx::Size mask_texture_size =
-            static_cast<viz::ContentDrawQuadBase*>(temp_quad)->texture_size;
+        // When the |temp_quad| is actually a texture, we need to calculate
+        // |mask_uv_rect|. The |mask_uv_rect| is the normalized sub-rect for
+        // applying the mask's texture. To get |mask_uv_rect|, we need the newly
+        // calculated |quad_rect| in the texture's space, then normalized by the
+        // texture's size.
+
+        // We are applying the |temp_quad|'s texture as a mask, so we start with
+        // the |tex_coord_rect| of the |temp_quad|.
         gfx::RectF temp_tex_coord_rect =
-            static_cast<viz::ContentDrawQuadBase*>(temp_quad)->tex_coord_rect;
-        gfx::Transform coverage_to_non_normalized_mask =
-            gfx::Transform(SkMatrix44(SkMatrix::MakeRectToRect(
-                RectToSkRect(quad_rect), RectFToSkRect(temp_tex_coord_rect),
-                SkMatrix::kFill_ScaleToFit)));
-        gfx::Transform coverage_to_normalized_mask =
-            coverage_to_non_normalized_mask;
-        coverage_to_normalized_mask.matrix().postScale(
-            1.f / mask_texture_size.width(), 1.f / mask_texture_size.height(),
-            1.f);
-        gfx::RectF mask_uv_rect = gfx::RectF(render_quad_rect);
-        coverage_to_normalized_mask.TransformRect(&mask_uv_rect);
+            viz::TileDrawQuad::MaterialCast(temp_quad)->tex_coord_rect;
+
+        // The |quad_rect| is in the same space as |temp_quad_rect|. Calculate
+        // the scale transform between the texture space and the quad space.
+        float scale_x = temp_tex_coord_rect.width() / temp_quad_rect.width();
+        float scale_y = temp_tex_coord_rect.height() / temp_quad_rect.height();
+        // Start by setting up the correct size of mask_uv_rect in texture
+        // space.
+        gfx::RectF mask_uv_rect(quad_rect.width() * scale_x,
+                                quad_rect.height() * scale_y);
+
+        // Now figure out what is the correct offset. Start with the original
+        // temp_tex_coord_rect's offset.
+        mask_uv_rect.Offset(temp_tex_coord_rect.OffsetFromOrigin());
+        // Next figure out what offset to apply by checking the difference in
+        // offset between |temp_quad_rect| and |quad_rect| which is
+        // intersected by the content_rect().
+        gfx::Vector2dF offset(quad_rect.OffsetFromOrigin());
+        offset -= temp_quad_rect.OffsetFromOrigin();
+        // Convert the difference in offset into texture space.
+        offset.Scale(scale_x, scale_y);
+        mask_uv_rect.Offset(offset);
+
+        // |mask_uv_rect| is normalized to [0..1] by the |mask_texture_size|.
+        gfx::Size mask_texture_size =
+            viz::TileDrawQuad::MaterialCast(temp_quad)->texture_size;
+        mask_uv_rect.Scale(1.f / mask_texture_size.width(),
+                           1.f / mask_texture_size.height());
 
         auto* quad =
             render_pass->CreateAndAppendDrawQuad<viz::RenderPassDrawQuad>();
-        quad->SetNew(shared_quad_state, render_quad_rect,
-                     quad_visible_rect_in_coverage_space, id(),
+        quad->SetNew(shared_quad_state, quad_rect, visible_quad_rect, id(),
                      temp_quad->resources.ids[0], mask_uv_rect,
                      mask_texture_size, owning_layer_to_surface_contents_scale,
-                     FiltersOrigin(), quad_rect_in_non_normalized_texture_space,
+                     FiltersOrigin(), tex_coord_rect,
                      !layer_tree_impl_->settings().enable_edge_anti_aliasing);
       } break;
       case viz::DrawQuad::SOLID_COLOR: {
-        if (!static_cast<viz::SolidColorDrawQuad*>(temp_quad)->color)
+        SkColor temp_color =
+            viz::SolidColorDrawQuad::MaterialCast(temp_quad)->color;
+        if (!temp_color)
           continue;
         SkAlpha solid = SK_AlphaOPAQUE;
-        DCHECK_EQ(SkColorGetA(
-                      static_cast<viz::SolidColorDrawQuad*>(temp_quad)->color),
-                  solid);
+        DCHECK_EQ(SkColorGetA(temp_color), solid);
 
         auto* quad =
             render_pass->CreateAndAppendDrawQuad<viz::RenderPassDrawQuad>();
-        quad->SetNew(shared_quad_state, render_quad_rect,
-                     quad_visible_rect_in_coverage_space, id(), 0, gfx::RectF(),
-                     gfx::Size(), owning_layer_to_surface_contents_scale,
-                     FiltersOrigin(), quad_rect_in_non_normalized_texture_space,
+        quad->SetNew(shared_quad_state, quad_rect, visible_quad_rect, id(), 0,
+                     gfx::RectF(), gfx::Size(),
+                     owning_layer_to_surface_contents_scale, FiltersOrigin(),
+                     tex_coord_rect,
                      !layer_tree_impl_->settings().enable_edge_anti_aliasing);
       } break;
       case viz::DrawQuad::DEBUG_BORDER:
diff --git a/cc/layers/render_surface_impl.h b/cc/layers/render_surface_impl.h
index 00b05df..8e7c1430 100644
--- a/cc/layers/render_surface_impl.h
+++ b/cc/layers/render_surface_impl.h
@@ -189,7 +189,7 @@
       const gfx::Transform& target_to_surface);
   void TileMaskLayer(viz::RenderPass* render_pass,
                      viz::SharedQuadState* shared_quad_state,
-                     const gfx::Rect& visible_layer_rect);
+                     const gfx::Rect& unoccluded_content_rect);
 
   LayerTreeImpl* layer_tree_impl_;
   uint64_t stable_id_;
diff --git a/cc/layers/render_surface_impl_unittest.cc b/cc/layers/render_surface_impl_unittest.cc
index 0690231..8fb231d 100644
--- a/cc/layers/render_surface_impl_unittest.cc
+++ b/cc/layers/render_surface_impl_unittest.cc
@@ -153,8 +153,11 @@
       viz::RenderPassDrawQuad::MaterialCast(render_pass->quad_list.front());
   EXPECT_EQ(gfx::Transform(),
             quad->shared_quad_state->quad_to_target_transform);
+  // With tiled mask layer, we only generate mask quads for visible rect. In
+  // this case |quad_layer_rect| is not fully covered, but
+  // |visible_quad_layer_rect| is fully covered.
   LayerTestCommon::VerifyQuadsExactlyCoverRect(
-      render_pass->quad_list, quad->shared_quad_state->quad_layer_rect);
+      render_pass->quad_list, quad->shared_quad_state->visible_quad_layer_rect);
 }
 
 }  // namespace
diff --git a/cc/layers/render_surface_unittest.cc b/cc/layers/render_surface_unittest.cc
index 714ddd4..14f74ec 100644
--- a/cc/layers/render_surface_unittest.cc
+++ b/cc/layers/render_surface_unittest.cc
@@ -245,6 +245,8 @@
   std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink =
       FakeLayerTreeFrameSink::Create3d();
   FakeLayerTreeHostImpl host_impl(&task_runner_provider, &task_graph_runner);
+  // Set a big enough viewport to show the entire render pass.
+  host_impl.SetViewportSize(gfx::Size(1000, 1000));
 
   std::unique_ptr<LayerImpl> root_layer =
       LayerImpl::Create(host_impl.active_tree(), 1);
@@ -312,6 +314,8 @@
   std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink =
       FakeLayerTreeFrameSink::Create3d();
   FakeLayerTreeHostImpl host_impl(&task_runner_provider, &task_graph_runner);
+  // Set a big enough viewport to show the entire render pass.
+  host_impl.SetViewportSize(gfx::Size(1000, 1000));
 
   std::unique_ptr<LayerImpl> root_layer =
       LayerImpl::Create(host_impl.active_tree(), 1);
diff --git a/cc/trees/draw_property_utils.cc b/cc/trees/draw_property_utils.cc
index d3c1958a..608f4cd 100644
--- a/cc/trees/draw_property_utils.cc
+++ b/cc/trees/draw_property_utils.cc
@@ -1004,7 +1004,7 @@
 }
 
 void ComputeMaskDrawProperties(LayerImpl* mask_layer,
-                               const PropertyTrees* property_trees) {
+                               PropertyTrees* property_trees) {
   // Mask draw properties are used only for rastering, so most of the draw
   // properties computed for other layers are not needed.
   // Draw transform of a mask layer has to be a 2d scale.
@@ -1016,8 +1016,17 @@
   mask_layer->draw_properties().screen_space_transform =
       ScreenSpaceTransformInternal(mask_layer,
                                    property_trees->transform_tree);
+
+  ConditionalClip clip = LayerClipRect(property_trees, mask_layer);
+  // is_clipped should be set before visible rect computation as it is used
+  // there.
+  mask_layer->draw_properties().is_clipped = clip.is_clipped;
+  mask_layer->draw_properties().clip_rect =
+      gfx::ToEnclosingRect(clip.clip_rect);
+  // Calculate actual visible layer rect for mask layers, since we could have
+  // tiled mask layers and the tile manager would need this info for rastering.
   mask_layer->draw_properties().visible_layer_rect =
-      gfx::Rect(mask_layer->bounds());
+      LayerVisibleRect(property_trees, mask_layer);
   mask_layer->draw_properties().opacity = 1;
 }
 
diff --git a/cc/trees/draw_property_utils.h b/cc/trees/draw_property_utils.h
index 45ed01f..6c84352 100644
--- a/cc/trees/draw_property_utils.h
+++ b/cc/trees/draw_property_utils.h
@@ -59,7 +59,7 @@
                                      PropertyTrees* property_trees);
 
 void CC_EXPORT ComputeMaskDrawProperties(LayerImpl* mask_layer,
-                                         const PropertyTrees* property_trees);
+                                         PropertyTrees* property_trees);
 
 void CC_EXPORT ComputeSurfaceDrawProperties(PropertyTrees* property_trees,
                                             RenderSurfaceImpl* render_surface);
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 1482c16a..84344ed 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -7391,478 +7391,6 @@
 
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestUpdateCopyRequests);
 
-class LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin
-    : public LayerTreeTest {
- protected:
-  void SetupTree() override {
-    // The masked layer has bounds 50x50, but it has a child that causes
-    // the surface bounds to be larger. It also has a parent that clips the
-    // masked layer and its surface.
-
-    scoped_refptr<Layer> root = Layer::Create();
-
-    scoped_refptr<FakePictureLayer> content_layer =
-        FakePictureLayer::Create(&client_);
-
-    std::unique_ptr<RecordingSource> recording_source =
-        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(100, 100));
-    PaintFlags paint1, paint2;
-    static_cast<FakeRecordingSource*>(recording_source.get())
-        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 100, 90), paint1);
-    static_cast<FakeRecordingSource*>(recording_source.get())
-        ->add_draw_rect_with_flags(gfx::Rect(0, 90, 100, 10), paint2);
-    client_.set_fill_with_nonsolid_color(true);
-    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
-
-    scoped_refptr<FakePictureLayer> mask_layer =
-        FakePictureLayer::CreateWithRecordingSource(
-            &client_, std::move(recording_source));
-    content_layer->SetMaskLayer(mask_layer.get());
-
-    gfx::Size root_size(100, 100);
-    root->SetBounds(root_size);
-
-    gfx::Size layer_size(100, 100);
-    content_layer->SetBounds(layer_size);
-
-    gfx::Size mask_size(100, 100);
-    mask_layer->SetBounds(mask_size);
-    mask_layer->SetLayerMaskType(Layer::LayerMaskType::MULTI_TEXTURE_MASK);
-    mask_layer_id_ = mask_layer->id();
-
-    layer_tree_host()->SetRootLayer(root);
-    LayerTreeTest::SetupTree();
-    scoped_refptr<Layer> outer_viewport_scroll_layer = Layer::Create();
-    outer_viewport_scroll_layer->SetBounds(layer_size);
-    CreateVirtualViewportLayers(root.get(), outer_viewport_scroll_layer,
-                                gfx::Size(50, 50), gfx::Size(50, 50),
-                                layer_tree_host());
-    layer_tree_host()->outer_viewport_container_layer()->SetMasksToBounds(true);
-    outer_viewport_scroll_layer->AddChild(content_layer);
-
-    client_.set_bounds(root->bounds());
-    outer_viewport_scroll_layer->SetScrollOffset(gfx::ScrollOffset(50, 50));
-  }
-
-  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
-
-  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
-                                   LayerTreeHostImpl::FrameData* frame_data,
-                                   DrawResult draw_result) override {
-    EXPECT_EQ(2u, frame_data->render_passes.size());
-    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
-    EXPECT_EQ(2u, root_pass->quad_list.size());
-
-    // There's a solid color quad under everything.
-    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
-              root_pass->quad_list.back()->material);
-
-    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
-              root_pass->quad_list.front()->material);
-    const viz::RenderPassDrawQuad* render_pass_quad =
-        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
-    EXPECT_EQ(gfx::Rect(50, 50, 50, 50).ToString(),
-              render_pass_quad->rect.ToString());
-    if (host_impl->settings().enable_mask_tiling) {
-      PictureLayerImpl* mask_layer_impl = static_cast<PictureLayerImpl*>(
-          host_impl->active_tree()->LayerById(mask_layer_id_));
-      gfx::SizeF texture_size(
-          mask_layer_impl->CalculateTileSize(mask_layer_impl->bounds()));
-      EXPECT_EQ(
-          gfx::RectF(50.f / texture_size.width(), 50.f / texture_size.height(),
-                     50.f / texture_size.width(), 50.f / texture_size.height())
-              .ToString(),
-          render_pass_quad->mask_uv_rect.ToString());
-    } else {
-      EXPECT_EQ(gfx::ScaleRect(gfx::RectF(50.f, 50.f, 50.f, 50.f), 1.f / 100.f)
-                    .ToString(),
-                render_pass_quad->mask_uv_rect.ToString());
-    }
-    EndTest();
-    return draw_result;
-  }
-
-  void AfterTest() override {}
-
-  int mask_layer_id_;
-  FakeContentLayerClient client_;
-};
-
-class LayerTreeTestSingleTextureMaskLayerForSurfaceWithContentRectNotAtOrigin
-    : public LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin {
- public:
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    settings->enable_mask_tiling = false;
-  }
-};
-
-SINGLE_AND_MULTI_THREAD_TEST_F(
-    LayerTreeTestSingleTextureMaskLayerForSurfaceWithContentRectNotAtOrigin);
-
-class LayerTreeTestMultiTextureMaskLayerForSurfaceWithContentRectNotAtOrigin
-    : public LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin {
- public:
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    settings->enable_mask_tiling = true;
-  }
-};
-
-SINGLE_AND_MULTI_THREAD_TEST_F(
-    LayerTreeTestMultiTextureMaskLayerForSurfaceWithContentRectNotAtOrigin);
-
-class LayerTreeTestMaskLayerForSurfaceWithClippedLayer : public LayerTreeTest {
- protected:
-  void SetupTree() override {
-    // The masked layer has bounds 50x50, but it has a child that causes
-    // the surface bounds to be larger. It also has a parent that clips the
-    // masked layer and its surface.
-
-    scoped_refptr<Layer> root = Layer::Create();
-
-    scoped_refptr<Layer> clipping_layer = Layer::Create();
-    root->AddChild(clipping_layer);
-
-    scoped_refptr<FakePictureLayer> content_layer =
-        FakePictureLayer::Create(&client_);
-    clipping_layer->AddChild(content_layer);
-
-    scoped_refptr<FakePictureLayer> content_child_layer =
-        FakePictureLayer::Create(&client_);
-    content_layer->AddChild(content_child_layer);
-
-    std::unique_ptr<RecordingSource> recording_source =
-        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(50, 50));
-    PaintFlags paint1, paint2;
-    static_cast<FakeRecordingSource*>(recording_source.get())
-        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 50, 40), paint1);
-    static_cast<FakeRecordingSource*>(recording_source.get())
-        ->add_draw_rect_with_flags(gfx::Rect(0, 40, 50, 10), paint2);
-    client_.set_fill_with_nonsolid_color(true);
-    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
-
-    scoped_refptr<FakePictureLayer> mask_layer =
-        FakePictureLayer::CreateWithRecordingSource(
-            &client_, std::move(recording_source));
-    content_layer->SetMaskLayer(mask_layer.get());
-
-    gfx::Size root_size(100, 100);
-    root->SetBounds(root_size);
-
-    gfx::PointF clipping_origin(20.f, 10.f);
-    gfx::Size clipping_size(10, 20);
-    clipping_layer->SetBounds(clipping_size);
-    clipping_layer->SetPosition(clipping_origin);
-    clipping_layer->SetMasksToBounds(true);
-
-    gfx::Size layer_size(50, 50);
-    content_layer->SetBounds(layer_size);
-    content_layer->SetPosition(gfx::PointF() -
-                               clipping_origin.OffsetFromOrigin());
-
-    gfx::Size child_size(50, 50);
-    content_child_layer->SetBounds(child_size);
-    content_child_layer->SetPosition(gfx::PointF(20.f, 0.f));
-
-    gfx::Size mask_size(50, 50);
-    mask_layer->SetBounds(mask_size);
-    mask_layer->SetLayerMaskType(Layer::LayerMaskType::MULTI_TEXTURE_MASK);
-    mask_layer_id_ = mask_layer->id();
-
-    layer_tree_host()->SetRootLayer(root);
-    LayerTreeTest::SetupTree();
-    client_.set_bounds(root->bounds());
-  }
-
-  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
-
-  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
-                                   LayerTreeHostImpl::FrameData* frame_data,
-                                   DrawResult draw_result) override {
-    EXPECT_EQ(2u, frame_data->render_passes.size());
-    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
-    EXPECT_EQ(2u, root_pass->quad_list.size());
-
-    // There's a solid color quad under everything.
-    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
-              root_pass->quad_list.back()->material);
-
-    // The surface is clipped to 10x20.
-    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
-              root_pass->quad_list.front()->material);
-    const viz::RenderPassDrawQuad* render_pass_quad =
-        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
-    EXPECT_EQ(gfx::Rect(20, 10, 10, 20).ToString(),
-              render_pass_quad->rect.ToString());
-    // The masked layer is 50x50, but the surface size is 10x20. So the texture
-    // coords in the mask are scaled by 10/50 and 20/50.
-    // The surface is clipped to (20,10) so the mask texture coords are offset
-    // by 20/50 and 10/50
-    if (host_impl->settings().enable_mask_tiling) {
-      PictureLayerImpl* mask_layer_impl = static_cast<PictureLayerImpl*>(
-          host_impl->active_tree()->LayerById(mask_layer_id_));
-      gfx::SizeF texture_size(
-          mask_layer_impl->CalculateTileSize(mask_layer_impl->bounds()));
-      EXPECT_EQ(
-          gfx::RectF(20.f / texture_size.width(), 10.f / texture_size.height(),
-                     10.f / texture_size.width(), 20.f / texture_size.height())
-              .ToString(),
-          render_pass_quad->mask_uv_rect.ToString());
-    } else {
-      EXPECT_EQ(gfx::ScaleRect(gfx::RectF(20.f, 10.f, 10.f, 20.f), 1.f / 50.f)
-                    .ToString(),
-                render_pass_quad->mask_uv_rect.ToString());
-    }
-    EndTest();
-    return draw_result;
-  }
-
-  void AfterTest() override {}
-
-  int mask_layer_id_;
-  FakeContentLayerClient client_;
-};
-
-class LayerTreeTestSingleTextureMaskLayerForSurfaceWithClippedLayer
-    : public LayerTreeTestMaskLayerForSurfaceWithClippedLayer {
- public:
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    settings->enable_mask_tiling = false;
-  }
-};
-
-SINGLE_AND_MULTI_THREAD_TEST_F(
-    LayerTreeTestSingleTextureMaskLayerForSurfaceWithClippedLayer);
-
-class LayerTreeTestMultiTextureMaskLayerForSurfaceWithClippedLayer
-    : public LayerTreeTestMaskLayerForSurfaceWithClippedLayer {
- public:
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    settings->enable_mask_tiling = true;
-  }
-};
-
-SINGLE_AND_MULTI_THREAD_TEST_F(
-    LayerTreeTestMultiTextureMaskLayerForSurfaceWithClippedLayer);
-
-class LayerTreeTestMaskLayerWithScaling : public LayerTreeTest {
- protected:
-  void SetupTree() override {
-    // Root
-    //  |
-    //  +-- Scaling Layer (adds a 2x scale)
-    //       |
-    //       +-- Content Layer
-    //             +--Mask
-
-    scoped_refptr<Layer> root = Layer::Create();
-
-    scoped_refptr<Layer> scaling_layer = Layer::Create();
-    root->AddChild(scaling_layer);
-
-    scoped_refptr<FakePictureLayer> content_layer =
-        FakePictureLayer::Create(&client_);
-    scaling_layer->AddChild(content_layer);
-
-    std::unique_ptr<RecordingSource> recording_source =
-        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(100, 100));
-    PaintFlags paint1, paint2;
-    static_cast<FakeRecordingSource*>(recording_source.get())
-        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 100, 10), paint1);
-    static_cast<FakeRecordingSource*>(recording_source.get())
-        ->add_draw_rect_with_flags(gfx::Rect(0, 10, 100, 90), paint2);
-    client_.set_fill_with_nonsolid_color(true);
-    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
-
-    scoped_refptr<FakePictureLayer> mask_layer =
-        FakePictureLayer::CreateWithRecordingSource(
-            &client_, std::move(recording_source));
-    content_layer->SetMaskLayer(mask_layer.get());
-
-    gfx::Size root_size(100, 100);
-    root->SetBounds(root_size);
-
-    gfx::Size scaling_layer_size(50, 50);
-    scaling_layer->SetBounds(scaling_layer_size);
-    gfx::Transform scale;
-    scale.Scale(2.f, 2.f);
-    scaling_layer->SetTransform(scale);
-
-    content_layer->SetBounds(scaling_layer_size);
-
-    mask_layer->SetBounds(scaling_layer_size);
-    mask_layer->SetLayerMaskType(Layer::LayerMaskType::MULTI_TEXTURE_MASK);
-
-    layer_tree_host()->SetRootLayer(root);
-    LayerTreeTest::SetupTree();
-    client_.set_bounds(root->bounds());
-  }
-
-  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
-
-  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
-                                   LayerTreeHostImpl::FrameData* frame_data,
-                                   DrawResult draw_result) override {
-    EXPECT_EQ(2u, frame_data->render_passes.size());
-    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
-    EXPECT_EQ(2u, root_pass->quad_list.size());
-
-    // There's a solid color quad under everything.
-    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
-              root_pass->quad_list.back()->material);
-
-    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
-              root_pass->quad_list.front()->material);
-    const viz::RenderPassDrawQuad* render_pass_quad =
-        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
-    switch (host_impl->active_tree()->source_frame_number()) {
-      case 0:
-        // Check that the tree scaling is correctly taken into account for the
-        // mask, that should fully map onto the quad.
-        EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
-                  render_pass_quad->rect.ToString());
-        if (host_impl->settings().enable_mask_tiling) {
-          EXPECT_EQ(
-              gfx::RectF(0.f, 0.f, 100.f / 128.f, 100.f / 128.f).ToString(),
-              render_pass_quad->mask_uv_rect.ToString());
-        } else {
-          EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
-                    render_pass_quad->mask_uv_rect.ToString());
-        }
-        break;
-      case 1:
-        // Applying a DSF should change the render surface size, but won't
-        // affect which part of the mask is used.
-        EXPECT_EQ(gfx::Rect(0, 0, 200, 200).ToString(),
-                  render_pass_quad->rect.ToString());
-        if (host_impl->settings().enable_mask_tiling) {
-          EXPECT_EQ(
-              gfx::RectF(0.f, 0.f, 100.f / 128.f, 100.f / 128.f).ToString(),
-              render_pass_quad->mask_uv_rect.ToString());
-        } else {
-          EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
-                    render_pass_quad->mask_uv_rect.ToString());
-        }
-        EndTest();
-        break;
-    }
-    return draw_result;
-  }
-
-  void DidCommit() override {
-    switch (layer_tree_host()->SourceFrameNumber()) {
-      case 1:
-        gfx::Size double_root_size(200, 200);
-        layer_tree_host()->SetViewportSizeAndScale(double_root_size, 2.f,
-                                                   viz::LocalSurfaceId());
-        break;
-    }
-  }
-
-  void AfterTest() override {}
-
-  FakeContentLayerClient client_;
-};
-
-class LayerTreeTestSingleTextureMaskLayerWithScaling
-    : public LayerTreeTestMaskLayerWithScaling {
- public:
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    settings->enable_mask_tiling = false;
-    settings->layer_transforms_should_scale_layer_contents = true;
-  }
-};
-
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestSingleTextureMaskLayerWithScaling);
-
-class LayerTreeTestMultiTextureMaskLayerWithScaling
-    : public LayerTreeTestMaskLayerWithScaling {
- public:
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    settings->enable_mask_tiling = true;
-    settings->layer_transforms_should_scale_layer_contents = true;
-  }
-};
-
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMultiTextureMaskLayerWithScaling);
-
-class LayerTreeTestMaskWithNonExactTextureSize : public LayerTreeTest {
- protected:
-  void SetupTree() override {
-    // The masked layer has bounds 100x100, but is allocated a 120x150 texture.
-
-    scoped_refptr<Layer> root = Layer::Create();
-
-    scoped_refptr<FakePictureLayer> content_layer =
-        FakePictureLayer::Create(&client_);
-    root->AddChild(content_layer);
-
-    std::unique_ptr<RecordingSource> recording_source =
-        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(100, 100));
-    PaintFlags paint1, paint2;
-    static_cast<FakeRecordingSource*>(recording_source.get())
-        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 100, 90), paint1);
-    static_cast<FakeRecordingSource*>(recording_source.get())
-        ->add_draw_rect_with_flags(gfx::Rect(0, 90, 100, 10), paint2);
-    client_.set_fill_with_nonsolid_color(true);
-    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
-
-    scoped_refptr<FakePictureLayer> mask_layer =
-        FakePictureLayer::CreateWithRecordingSource(
-            &client_, std::move(recording_source));
-    content_layer->SetMaskLayer(mask_layer.get());
-
-    gfx::Size root_size(100, 100);
-    root->SetBounds(root_size);
-
-    gfx::Size layer_size(100, 100);
-    content_layer->SetBounds(layer_size);
-
-    gfx::Size mask_size(100, 100);
-    gfx::Size mask_texture_size(120, 150);
-    mask_layer->SetBounds(mask_size);
-    mask_layer->SetLayerMaskType(Layer::LayerMaskType::SINGLE_TEXTURE_MASK);
-    mask_layer->set_fixed_tile_size(mask_texture_size);
-
-    layer_tree_host()->SetRootLayer(root);
-    LayerTreeTest::SetupTree();
-    client_.set_bounds(root->bounds());
-  }
-
-  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
-
-  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
-                                   LayerTreeHostImpl::FrameData* frame_data,
-                                   DrawResult draw_result) override {
-    EXPECT_EQ(2u, frame_data->render_passes.size());
-    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
-    EXPECT_EQ(2u, root_pass->quad_list.size());
-
-    // There's a solid color quad under everything.
-    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
-              root_pass->quad_list.back()->material);
-
-    // The surface is 100x100
-    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
-              root_pass->quad_list.front()->material);
-    const viz::RenderPassDrawQuad* render_pass_quad =
-        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
-    EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
-              render_pass_quad->rect.ToString());
-    // The mask layer is 100x100, but is backed by a 120x150 image.
-    EXPECT_EQ(gfx::RectF(0.0f, 0.0f, 100.f / 120.0f, 100.f / 150.0f).ToString(),
-              render_pass_quad->mask_uv_rect.ToString());
-    EndTest();
-    return draw_result;
-  }
-
-  void AfterTest() override {}
-
-  int mask_layer_id_;
-  FakeContentLayerClient client_;
-};
-
-SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMaskWithNonExactTextureSize);
-
 class LayerTreeTestPageScaleFlags : public LayerTreeTest {
  protected:
   void SetupTree() override {
diff --git a/cc/trees/layer_tree_host_unittest_masks.cc b/cc/trees/layer_tree_host_unittest_masks.cc
new file mode 100644
index 0000000..b1615f0e
--- /dev/null
+++ b/cc/trees/layer_tree_host_unittest_masks.cc
@@ -0,0 +1,666 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/trees/layer_tree_host.h"
+
+#include "cc/test/fake_picture_layer.h"
+#include "cc/test/fake_recording_source.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "components/viz/common/quads/render_pass_draw_quad.h"
+
+namespace cc {
+namespace {
+
+class LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin
+    : public LayerTreeTest {
+ protected:
+  void SetupTree() override {
+    // The masked layer has bounds 50x50, but it has a child that causes
+    // the surface bounds to be larger. It also has a parent that clips the
+    // masked layer and its surface.
+
+    scoped_refptr<Layer> root = Layer::Create();
+
+    scoped_refptr<FakePictureLayer> content_layer =
+        FakePictureLayer::Create(&client_);
+
+    std::unique_ptr<RecordingSource> recording_source =
+        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(100, 100));
+    PaintFlags paint1, paint2;
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 100, 90), paint1);
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 90, 100, 10), paint2);
+    client_.set_fill_with_nonsolid_color(true);
+    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
+
+    scoped_refptr<FakePictureLayer> mask_layer =
+        FakePictureLayer::CreateWithRecordingSource(
+            &client_, std::move(recording_source));
+    content_layer->SetMaskLayer(mask_layer.get());
+
+    gfx::Size root_size(100, 100);
+    root->SetBounds(root_size);
+
+    gfx::Size layer_size(100, 100);
+    content_layer->SetBounds(layer_size);
+
+    gfx::Size mask_size(100, 100);
+    mask_layer->SetBounds(mask_size);
+    mask_layer->SetLayerMaskType(Layer::LayerMaskType::MULTI_TEXTURE_MASK);
+    mask_layer_id_ = mask_layer->id();
+
+    layer_tree_host()->SetRootLayer(root);
+    LayerTreeTest::SetupTree();
+    scoped_refptr<Layer> outer_viewport_scroll_layer = Layer::Create();
+    outer_viewport_scroll_layer->SetBounds(layer_size);
+    CreateVirtualViewportLayers(root.get(), outer_viewport_scroll_layer,
+                                gfx::Size(50, 50), gfx::Size(50, 50),
+                                layer_tree_host());
+    layer_tree_host()->outer_viewport_container_layer()->SetMasksToBounds(true);
+    outer_viewport_scroll_layer->AddChild(content_layer);
+
+    client_.set_bounds(root->bounds());
+    outer_viewport_scroll_layer->SetScrollOffset(gfx::ScrollOffset(50, 50));
+  }
+
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+
+  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+                                   LayerTreeHostImpl::FrameData* frame_data,
+                                   DrawResult draw_result) override {
+    EXPECT_EQ(2u, frame_data->render_passes.size());
+    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
+    EXPECT_EQ(2u, root_pass->quad_list.size());
+
+    // There's a solid color quad under everything.
+    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
+              root_pass->quad_list.back()->material);
+
+    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
+              root_pass->quad_list.front()->material);
+    const viz::RenderPassDrawQuad* render_pass_quad =
+        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
+    gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect(
+        render_pass_quad->shared_quad_state->quad_to_target_transform,
+        render_pass_quad->rect);
+    EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(),
+              rect_in_target_space.ToString());
+    if (host_impl->settings().enable_mask_tiling) {
+      PictureLayerImpl* mask_layer_impl = static_cast<PictureLayerImpl*>(
+          host_impl->active_tree()->LayerById(mask_layer_id_));
+      gfx::SizeF texture_size(
+          mask_layer_impl->CalculateTileSize(mask_layer_impl->bounds()));
+      EXPECT_EQ(
+          gfx::RectF(50.f / texture_size.width(), 50.f / texture_size.height(),
+                     50.f / texture_size.width(), 50.f / texture_size.height())
+              .ToString(),
+          render_pass_quad->mask_uv_rect.ToString());
+    } else {
+      EXPECT_EQ(gfx::ScaleRect(gfx::RectF(50.f, 50.f, 50.f, 50.f), 1.f / 100.f)
+                    .ToString(),
+                render_pass_quad->mask_uv_rect.ToString());
+    }
+    EndTest();
+    return draw_result;
+  }
+
+  void AfterTest() override {}
+
+  int mask_layer_id_;
+  FakeContentLayerClient client_;
+};
+
+class LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin_Untiled
+    : public LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = false;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin_Untiled);
+
+class LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin_Tiled
+    : public LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = true;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin_Tiled);
+
+class LayerTreeTestMaskLayerForSurfaceWithClippedLayer : public LayerTreeTest {
+ protected:
+  void SetupTree() override {
+    // The masked layer has bounds 50x50, but it has a child that causes
+    // the surface bounds to be larger. It also has a parent that clips the
+    // masked layer and its surface.
+
+    scoped_refptr<Layer> root = Layer::Create();
+
+    scoped_refptr<Layer> clipping_layer = Layer::Create();
+    root->AddChild(clipping_layer);
+
+    scoped_refptr<FakePictureLayer> content_layer =
+        FakePictureLayer::Create(&client_);
+    clipping_layer->AddChild(content_layer);
+
+    scoped_refptr<FakePictureLayer> content_child_layer =
+        FakePictureLayer::Create(&client_);
+    content_layer->AddChild(content_child_layer);
+
+    std::unique_ptr<RecordingSource> recording_source =
+        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(50, 50));
+    PaintFlags paint1, paint2;
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 50, 40), paint1);
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 40, 50, 10), paint2);
+    client_.set_fill_with_nonsolid_color(true);
+    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
+
+    scoped_refptr<FakePictureLayer> mask_layer =
+        FakePictureLayer::CreateWithRecordingSource(
+            &client_, std::move(recording_source));
+    content_layer->SetMaskLayer(mask_layer.get());
+
+    gfx::Size root_size(100, 100);
+    root->SetBounds(root_size);
+
+    gfx::PointF clipping_origin(20.f, 10.f);
+    gfx::Size clipping_size(10, 20);
+    clipping_layer->SetBounds(clipping_size);
+    clipping_layer->SetPosition(clipping_origin);
+    clipping_layer->SetMasksToBounds(true);
+
+    gfx::Size layer_size(50, 50);
+    content_layer->SetBounds(layer_size);
+    content_layer->SetPosition(gfx::PointF() -
+                               clipping_origin.OffsetFromOrigin());
+
+    gfx::Size child_size(50, 50);
+    content_child_layer->SetBounds(child_size);
+    content_child_layer->SetPosition(gfx::PointF(20.f, 0.f));
+
+    gfx::Size mask_size(50, 50);
+    mask_layer->SetBounds(mask_size);
+    mask_layer->SetLayerMaskType(Layer::LayerMaskType::MULTI_TEXTURE_MASK);
+    mask_layer_id_ = mask_layer->id();
+
+    layer_tree_host()->SetRootLayer(root);
+    LayerTreeTest::SetupTree();
+    client_.set_bounds(root->bounds());
+  }
+
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+
+  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+                                   LayerTreeHostImpl::FrameData* frame_data,
+                                   DrawResult draw_result) override {
+    EXPECT_EQ(2u, frame_data->render_passes.size());
+    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
+    EXPECT_EQ(2u, root_pass->quad_list.size());
+
+    // There's a solid color quad under everything.
+    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
+              root_pass->quad_list.back()->material);
+
+    // The surface is clipped to 10x20.
+    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
+              root_pass->quad_list.front()->material);
+    const viz::RenderPassDrawQuad* render_pass_quad =
+        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
+    gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect(
+        render_pass_quad->shared_quad_state->quad_to_target_transform,
+        render_pass_quad->rect);
+    EXPECT_EQ(gfx::Rect(20, 10, 10, 20).ToString(),
+              rect_in_target_space.ToString());
+    // The masked layer is 50x50, but the surface size is 10x20. So the texture
+    // coords in the mask are scaled by 10/50 and 20/50.
+    // The surface is clipped to (20,10) so the mask texture coords are offset
+    // by 20/50 and 10/50
+    if (host_impl->settings().enable_mask_tiling) {
+      PictureLayerImpl* mask_layer_impl = static_cast<PictureLayerImpl*>(
+          host_impl->active_tree()->LayerById(mask_layer_id_));
+      gfx::SizeF texture_size(
+          mask_layer_impl->CalculateTileSize(mask_layer_impl->bounds()));
+      EXPECT_EQ(
+          gfx::RectF(20.f / texture_size.width(), 10.f / texture_size.height(),
+                     10.f / texture_size.width(), 20.f / texture_size.height())
+              .ToString(),
+          render_pass_quad->mask_uv_rect.ToString());
+    } else {
+      EXPECT_EQ(gfx::ScaleRect(gfx::RectF(20.f, 10.f, 10.f, 20.f), 1.f / 50.f)
+                    .ToString(),
+                render_pass_quad->mask_uv_rect.ToString());
+    }
+    EndTest();
+    return draw_result;
+  }
+
+  void AfterTest() override {}
+
+  int mask_layer_id_;
+  FakeContentLayerClient client_;
+};
+
+class LayerTreeTestMaskLayerForSurfaceWithClippedLayer_Untiled
+    : public LayerTreeTestMaskLayerForSurfaceWithClippedLayer {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = false;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeTestMaskLayerForSurfaceWithClippedLayer_Untiled);
+
+class LayerTreeTestMaskLayerForSurfaceWithClippedLayer_Tiled
+    : public LayerTreeTestMaskLayerForSurfaceWithClippedLayer {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = true;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeTestMaskLayerForSurfaceWithClippedLayer_Tiled);
+
+class LayerTreeTestMaskLayerForSurfaceWithDifferentScale
+    : public LayerTreeTest {
+ protected:
+  void SetupTree() override {
+    // The masked layer has bounds 50x50, but it has a child that causes
+    // the surface bounds to be larger. It also has a parent that clips the
+    // masked layer and its surface.
+
+    scoped_refptr<Layer> root = Layer::Create();
+
+    scoped_refptr<Layer> clipping_scaling_layer = Layer::Create();
+    root->AddChild(clipping_scaling_layer);
+
+    scoped_refptr<FakePictureLayer> content_layer =
+        FakePictureLayer::Create(&client_);
+    clipping_scaling_layer->AddChild(content_layer);
+
+    scoped_refptr<FakePictureLayer> content_child_layer =
+        FakePictureLayer::Create(&client_);
+    content_layer->AddChild(content_child_layer);
+
+    std::unique_ptr<RecordingSource> recording_source =
+        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(50, 50));
+    PaintFlags paint1, paint2;
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 50, 40), paint1);
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 40, 50, 10), paint2);
+    client_.set_fill_with_nonsolid_color(true);
+    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
+
+    scoped_refptr<FakePictureLayer> mask_layer =
+        FakePictureLayer::CreateWithRecordingSource(
+            &client_, std::move(recording_source));
+    content_layer->SetMaskLayer(mask_layer.get());
+
+    gfx::Size root_size(100, 100);
+    root->SetBounds(root_size);
+
+    gfx::Transform scale;
+    scale.Scale(2, 2);
+    gfx::PointF clipping_origin(20.f, 10.f);
+    gfx::Size clipping_size(10, 20);
+    clipping_scaling_layer->SetBounds(clipping_size);
+    clipping_scaling_layer->SetPosition(clipping_origin);
+    // This changes scale between contributing layer and render surface to 2.
+    clipping_scaling_layer->SetTransform(scale);
+    clipping_scaling_layer->SetMasksToBounds(true);
+
+    gfx::Size layer_size(50, 50);
+    content_layer->SetBounds(layer_size);
+    content_layer->SetPosition(gfx::PointF() -
+                               clipping_origin.OffsetFromOrigin());
+
+    gfx::Size child_size(50, 50);
+    content_child_layer->SetBounds(child_size);
+    content_child_layer->SetPosition(gfx::PointF(20.f, 0.f));
+
+    gfx::Size mask_size(50, 50);
+    mask_layer->SetBounds(mask_size);
+    mask_layer->SetLayerMaskType(Layer::LayerMaskType::MULTI_TEXTURE_MASK);
+    // Setting will change transform on mask layer will make it not adjust
+    // raster scale, which will remain 1. This means the mask_layer and render
+    // surface will have a scale of 2 during draw time.
+    mask_layer->SetHasWillChangeTransformHint(true);
+    mask_layer_id_ = mask_layer->id();
+
+    layer_tree_host()->SetRootLayer(root);
+    LayerTreeTest::SetupTree();
+    client_.set_bounds(root->bounds());
+  }
+
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+
+  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+                                   LayerTreeHostImpl::FrameData* frame_data,
+                                   DrawResult draw_result) override {
+    EXPECT_EQ(2u, frame_data->render_passes.size());
+    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
+    EXPECT_EQ(2u, root_pass->quad_list.size());
+
+    // There's a solid color quad under everything.
+    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
+              root_pass->quad_list.back()->material);
+
+    // The surface is clipped to 10x20, and then scaled by 2, which ends up
+    // being 20x40.
+    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
+              root_pass->quad_list.front()->material);
+    const viz::RenderPassDrawQuad* render_pass_quad =
+        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
+    gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect(
+        render_pass_quad->shared_quad_state->quad_to_target_transform,
+        render_pass_quad->rect);
+    EXPECT_EQ(gfx::Rect(20, 10, 20, 40).ToString(),
+              rect_in_target_space.ToString());
+    gfx::Rect visible_rect_in_target_space = MathUtil::MapEnclosingClippedRect(
+        render_pass_quad->shared_quad_state->quad_to_target_transform,
+        render_pass_quad->visible_rect);
+    EXPECT_EQ(gfx::Rect(20, 10, 20, 40).ToString(),
+              visible_rect_in_target_space.ToString());
+    // The masked layer is 50x50, but the surface size is 10x20. So the texture
+    // coords in the mask are scaled by 10/50 and 20/50.
+    // The surface is clipped to (20,10) so the mask texture coords are offset
+    // by 20/50 and 10/50
+    if (host_impl->settings().enable_mask_tiling) {
+      PictureLayerImpl* mask_layer_impl = static_cast<PictureLayerImpl*>(
+          host_impl->active_tree()->LayerById(mask_layer_id_));
+      gfx::SizeF texture_size(
+          mask_layer_impl->CalculateTileSize(mask_layer_impl->bounds()));
+      EXPECT_EQ(
+          gfx::RectF(20.f / texture_size.width(), 10.f / texture_size.height(),
+                     10.f / texture_size.width(), 20.f / texture_size.height())
+              .ToString(),
+          render_pass_quad->mask_uv_rect.ToString());
+    } else {
+      EXPECT_EQ(gfx::ScaleRect(gfx::RectF(20.f, 10.f, 10.f, 20.f), 1.f / 50.f)
+                    .ToString(),
+                render_pass_quad->mask_uv_rect.ToString());
+    }
+    EndTest();
+    return draw_result;
+  }
+
+  void AfterTest() override {}
+
+  int mask_layer_id_;
+  FakeContentLayerClient client_;
+};
+
+class LayerTreeTestMaskLayerForSurfaceWithDifferentScale_Untiled
+    : public LayerTreeTestMaskLayerForSurfaceWithDifferentScale {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = false;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeTestMaskLayerForSurfaceWithDifferentScale_Untiled);
+
+class LayerTreeTestMaskLayerForSurfaceWithDifferentScale_Tiled
+    : public LayerTreeTestMaskLayerForSurfaceWithDifferentScale {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = true;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeTestMaskLayerForSurfaceWithDifferentScale_Tiled);
+
+class LayerTreeTestMaskLayerWithScaling : public LayerTreeTest {
+ protected:
+  void SetupTree() override {
+    // Root
+    //  |
+    //  +-- Scaling Layer (adds a 2x scale)
+    //       |
+    //       +-- Content Layer
+    //             +--Mask
+
+    scoped_refptr<Layer> root = Layer::Create();
+
+    scoped_refptr<Layer> scaling_layer = Layer::Create();
+    root->AddChild(scaling_layer);
+
+    scoped_refptr<FakePictureLayer> content_layer =
+        FakePictureLayer::Create(&client_);
+    scaling_layer->AddChild(content_layer);
+
+    std::unique_ptr<RecordingSource> recording_source =
+        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(100, 100));
+    PaintFlags paint1, paint2;
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 100, 10), paint1);
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 10, 100, 90), paint2);
+    client_.set_fill_with_nonsolid_color(true);
+    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
+
+    scoped_refptr<FakePictureLayer> mask_layer =
+        FakePictureLayer::CreateWithRecordingSource(
+            &client_, std::move(recording_source));
+    content_layer->SetMaskLayer(mask_layer.get());
+
+    gfx::Size root_size(100, 100);
+    root->SetBounds(root_size);
+
+    gfx::Size scaling_layer_size(50, 50);
+    scaling_layer->SetBounds(scaling_layer_size);
+    gfx::Transform scale;
+    scale.Scale(2.f, 2.f);
+    scaling_layer->SetTransform(scale);
+
+    content_layer->SetBounds(scaling_layer_size);
+
+    mask_layer->SetBounds(scaling_layer_size);
+    mask_layer->SetLayerMaskType(Layer::LayerMaskType::MULTI_TEXTURE_MASK);
+
+    layer_tree_host()->SetRootLayer(root);
+    LayerTreeTest::SetupTree();
+    client_.set_bounds(root->bounds());
+  }
+
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+
+  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+                                   LayerTreeHostImpl::FrameData* frame_data,
+                                   DrawResult draw_result) override {
+    EXPECT_EQ(2u, frame_data->render_passes.size());
+    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
+    EXPECT_EQ(2u, root_pass->quad_list.size());
+
+    // There's a solid color quad under everything.
+    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
+              root_pass->quad_list.back()->material);
+
+    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
+              root_pass->quad_list.front()->material);
+    const viz::RenderPassDrawQuad* render_pass_quad =
+        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
+    switch (host_impl->active_tree()->source_frame_number()) {
+      case 0:
+        // Check that the tree scaling is correctly taken into account for the
+        // mask, that should fully map onto the quad.
+        EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+                  render_pass_quad->rect.ToString());
+        if (host_impl->settings().enable_mask_tiling) {
+          EXPECT_EQ(
+              gfx::RectF(0.f, 0.f, 100.f / 128.f, 100.f / 128.f).ToString(),
+              render_pass_quad->mask_uv_rect.ToString());
+        } else {
+          EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+                    render_pass_quad->mask_uv_rect.ToString());
+        }
+        break;
+      case 1:
+        // Applying a DSF should change the render surface size, but won't
+        // affect which part of the mask is used.
+        EXPECT_EQ(gfx::Rect(0, 0, 200, 200).ToString(),
+                  render_pass_quad->rect.ToString());
+        if (host_impl->settings().enable_mask_tiling) {
+          EXPECT_EQ(
+              gfx::RectF(0.f, 0.f, 100.f / 128.f, 100.f / 128.f).ToString(),
+              render_pass_quad->mask_uv_rect.ToString());
+        } else {
+          EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+                    render_pass_quad->mask_uv_rect.ToString());
+        }
+        EndTest();
+        break;
+    }
+    return draw_result;
+  }
+
+  void DidCommit() override {
+    switch (layer_tree_host()->SourceFrameNumber()) {
+      case 1:
+        gfx::Size double_root_size(200, 200);
+        layer_tree_host()->SetViewportSizeAndScale(double_root_size, 2.f,
+                                                   viz::LocalSurfaceId());
+        break;
+    }
+  }
+
+  void AfterTest() override {}
+
+  FakeContentLayerClient client_;
+};
+
+class LayerTreeTestMaskLayerWithScaling_Untiled
+    : public LayerTreeTestMaskLayerWithScaling {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = false;
+    settings->layer_transforms_should_scale_layer_contents = true;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMaskLayerWithScaling_Untiled);
+
+class LayerTreeTestMaskLayerWithScaling_Tiled
+    : public LayerTreeTestMaskLayerWithScaling {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = true;
+    settings->layer_transforms_should_scale_layer_contents = true;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMaskLayerWithScaling_Tiled);
+
+class LayerTreeTestMaskWithNonExactTextureSize : public LayerTreeTest {
+ protected:
+  void SetupTree() override {
+    // The masked layer has bounds 100x100, but is allocated a 120x150 texture.
+
+    scoped_refptr<Layer> root = Layer::Create();
+
+    scoped_refptr<FakePictureLayer> content_layer =
+        FakePictureLayer::Create(&client_);
+    root->AddChild(content_layer);
+
+    std::unique_ptr<RecordingSource> recording_source =
+        FakeRecordingSource::CreateFilledRecordingSource(gfx::Size(100, 100));
+    PaintFlags paint1, paint2;
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 0, 100, 90), paint1);
+    static_cast<FakeRecordingSource*>(recording_source.get())
+        ->add_draw_rect_with_flags(gfx::Rect(0, 90, 100, 10), paint2);
+    client_.set_fill_with_nonsolid_color(true);
+    static_cast<FakeRecordingSource*>(recording_source.get())->Rerecord();
+
+    scoped_refptr<FakePictureLayer> mask_layer =
+        FakePictureLayer::CreateWithRecordingSource(
+            &client_, std::move(recording_source));
+    content_layer->SetMaskLayer(mask_layer.get());
+
+    gfx::Size root_size(100, 100);
+    root->SetBounds(root_size);
+
+    gfx::Size layer_size(100, 100);
+    content_layer->SetBounds(layer_size);
+
+    gfx::Size mask_size(100, 100);
+    gfx::Size mask_texture_size(120, 150);
+    mask_layer->SetBounds(mask_size);
+    mask_layer->SetLayerMaskType(Layer::LayerMaskType::SINGLE_TEXTURE_MASK);
+    mask_layer->set_fixed_tile_size(mask_texture_size);
+
+    layer_tree_host()->SetRootLayer(root);
+    LayerTreeTest::SetupTree();
+    client_.set_bounds(root->bounds());
+  }
+
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+
+  DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+                                   LayerTreeHostImpl::FrameData* frame_data,
+                                   DrawResult draw_result) override {
+    EXPECT_EQ(2u, frame_data->render_passes.size());
+    viz::RenderPass* root_pass = frame_data->render_passes.back().get();
+    EXPECT_EQ(2u, root_pass->quad_list.size());
+
+    // There's a solid color quad under everything.
+    EXPECT_EQ(viz::DrawQuad::SOLID_COLOR,
+              root_pass->quad_list.back()->material);
+
+    // The surface is 100x100
+    EXPECT_EQ(viz::DrawQuad::RENDER_PASS,
+              root_pass->quad_list.front()->material);
+    const viz::RenderPassDrawQuad* render_pass_quad =
+        viz::RenderPassDrawQuad::MaterialCast(root_pass->quad_list.front());
+    EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+              render_pass_quad->rect.ToString());
+    // The mask layer is 100x100, but is backed by a 120x150 image.
+    EXPECT_EQ(gfx::RectF(0.0f, 0.0f, 100.f / 120.0f, 100.f / 150.0f).ToString(),
+              render_pass_quad->mask_uv_rect.ToString());
+    EndTest();
+    return draw_result;
+  }
+
+  void AfterTest() override {}
+
+  int mask_layer_id_;
+  FakeContentLayerClient client_;
+};
+
+class LayerTreeTestMaskWithNonExactTextureSize_Untiled
+    : public LayerTreeTestMaskWithNonExactTextureSize {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = false;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeTestMaskWithNonExactTextureSize_Untiled);
+
+class LayerTreeTestMaskWithNonExactTextureSize_Tiled
+    : public LayerTreeTestMaskWithNonExactTextureSize {
+ public:
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->enable_mask_tiling = true;
+  }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMaskWithNonExactTextureSize_Tiled);
+
+}  // namespace
+}  // namespace cc