[go: nahoru, domu]

Add Viz unit tests for parent_clip_rect

With kAllowUndamagedNonrootRenderPassToSkip enabled, The render
pass with pixel-moving filters should have different damage_rect,
and the render pass should redraw if |parent_clip_rect| expands.

Bug: 1346502
Change-Id: I115f3127537fd41fef73e7c5488890e6384efce7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4576663
Commit-Queue: Maggie Chen <magchen@chromium.org>
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1152047}
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index f6aa0d5..1466374 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -297,7 +297,7 @@
 bool RenderPassNeedsFullDamage(ResolvedPassData& resolved_pass) {
   auto& aggregation = resolved_pass.aggregation();
 
-  static const bool can_skip_render_pass = base::FeatureList::IsEnabled(
+  const bool can_skip_render_pass = base::FeatureList::IsEnabled(
       features::kAllowUndamagedNonrootRenderPassToSkip);
   if (can_skip_render_pass) {
     // Needs full damage when
diff --git a/components/viz/service/display/surface_aggregator_unittest.cc b/components/viz/service/display/surface_aggregator_unittest.cc
index 8a2125f..3c60b0847 100644
--- a/components/viz/service/display/surface_aggregator_unittest.cc
+++ b/components/viz/service/display/surface_aggregator_unittest.cc
@@ -5691,6 +5691,483 @@
   }
 }
 
+// Tests that quads outside the damage rect are ignored.
+TEST_F(SurfaceAggregatorPartialSwapTest, AllowSkipAndIgnoreOutside) {
+  // The damage is different for the pixel-moving filter mode with
+  // kAllowUndamagedNonrootRenderPassToSkip enabled.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kAllowUndamagedNonrootRenderPassToSkip);
+
+  TestSurfaceIdAllocator child_surface_id(child_sink_->frame_sink_id());
+  constexpr float device_scale_factor = 1.0f;
+
+  // The child surface has three quads, one with a visible rect of 13,13 4x4 and
+  // the other other with a visible rect of 10,10 2x2 (relative to root target
+  // space), and one with a non-invertible transform.
+  {
+    CompositorRenderPassId child_pass_ids[] = {CompositorRenderPassId{1},
+                                               CompositorRenderPassId{2},
+                                               CompositorRenderPassId{3}};
+    std::vector<Quad> child_quads1 = {
+        Quad::SolidColorQuad(SkColors::kGreen, gfx::Rect(5, 5))};
+    std::vector<Quad> child_quads2 = {
+        Quad::SolidColorQuad(SkColors::kGreen, gfx::Rect(5, 5))};
+    std::vector<Quad> child_quads3 = {
+        Quad::SolidColorQuad(SkColors::kGreen, gfx::Rect(5, 5))};
+    std::vector<Pass> child_passes = {
+        Pass(child_quads1, child_pass_ids[0], kSurfaceSize),
+        Pass(child_quads2, child_pass_ids[1], kSurfaceSize),
+        Pass(child_quads3, child_pass_ids[2], kSurfaceSize)};
+
+    CompositorRenderPassList child_pass_list;
+    std::vector<SurfaceRange> referenced_surfaces;
+    AddPasses(&child_pass_list, child_passes, &referenced_surfaces);
+
+    child_pass_list[0]->quad_list.ElementAt(0)->visible_rect =
+        gfx::Rect(1, 1, 2, 2);
+    auto* child_sqs = child_pass_list[0]->shared_quad_state_list.ElementAt(0u);
+    child_sqs->quad_to_target_transform.Translate(1, 1);
+    child_sqs->quad_to_target_transform.Scale(2, 2);
+
+    child_pass_list[1]->quad_list.ElementAt(0)->visible_rect =
+        gfx::Rect(0, 0, 2, 2);
+
+    auto* child_noninvertible_sqs =
+        child_pass_list[2]->shared_quad_state_list.ElementAt(0u);
+    child_noninvertible_sqs->quad_to_target_transform.set_rc(0, 0, 0.0);
+    EXPECT_FALSE(
+        child_noninvertible_sqs->quad_to_target_transform.IsInvertible());
+    child_pass_list[2]->quad_list.ElementAt(0)->visible_rect =
+        gfx::Rect(0, 0, 2, 2);
+
+    // Check whether a nonroot render pass can be skipped or not.
+    child_pass_list[0]->has_damage_from_contributing_content = true;
+    child_pass_list[1]->has_damage_from_contributing_content = true;
+    child_pass_list[2]->has_damage_from_contributing_content = true;
+
+    SubmitPassListAsFrame(child_sink_.get(),
+                          child_surface_id.local_surface_id(), &child_pass_list,
+                          std::move(referenced_surfaces), device_scale_factor);
+  }
+
+  {
+    std::vector<Quad> root_quads = {Quad::SurfaceQuad(
+        SurfaceRange(absl::nullopt, child_surface_id), SkColors::kWhite,
+        gfx::Rect(5, 5), /*stretch_content_to_fill_bounds=*/false)};
+
+    std::vector<Pass> root_passes = {Pass(root_quads, kSurfaceSize)};
+
+    CompositorRenderPassList root_pass_list;
+    std::vector<SurfaceRange> referenced_surfaces;
+    AddPasses(&root_pass_list, root_passes, &referenced_surfaces);
+
+    auto* root_pass = root_pass_list[0].get();
+    root_pass->shared_quad_state_list.front()
+        ->quad_to_target_transform.Translate(10, 10);
+    root_pass->damage_rect = gfx::Rect(0, 0, 1, 1);
+    root_pass->has_damage_from_contributing_content = true;
+
+    SubmitPassListAsFrame(root_sink_.get(), root_surface_id_.local_surface_id(),
+                          &root_pass_list, std::move(referenced_surfaces),
+                          device_scale_factor);
+  }
+
+  {
+    auto aggregated_frame = AggregateFrame(root_surface_id_);
+
+    const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+    ASSERT_EQ(3u, aggregated_pass_list.size());
+
+    // Damage rect for first aggregation should contain entire root surface.
+    EXPECT_EQ(gfx::Rect(kSurfaceSize), aggregated_pass_list[2]->damage_rect);
+    EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+
+    EXPECT_TRUE(aggregated_pass_list[0]->has_damage_from_contributing_content);
+    EXPECT_TRUE(aggregated_pass_list[1]->has_damage_from_contributing_content);
+  }
+
+  // Create a root surface with a smaller damage rect.
+  {
+    std::vector<Quad> root_quads = {Quad::SurfaceQuad(
+        SurfaceRange(absl::nullopt, child_surface_id), SkColors::kWhite,
+        gfx::Rect(5, 5), /*stretch_content_to_fill_bounds=*/false)};
+
+    std::vector<Pass> root_passes = {Pass(root_quads, kSurfaceSize)};
+
+    CompositorRenderPassList root_pass_list;
+    std::vector<SurfaceRange> referenced_surfaces;
+    AddPasses(&root_pass_list, root_passes, &referenced_surfaces);
+
+    auto* root_pass = root_pass_list[0].get();
+    root_pass->shared_quad_state_list.front()
+        ->quad_to_target_transform.Translate(10, 10);
+    root_pass->damage_rect = gfx::Rect(10, 10, 2, 2);
+    SubmitPassListAsFrame(root_sink_.get(), root_surface_id_.local_surface_id(),
+                          &root_pass_list, std::move(referenced_surfaces),
+                          device_scale_factor);
+  }
+
+  {
+    auto aggregated_frame = AggregateFrame(root_surface_id_);
+
+    const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+    ASSERT_EQ(3u, aggregated_pass_list.size());
+
+    // Only first quad from surface is inside damage rect and should be
+    // included.
+    EXPECT_EQ(gfx::Rect(10, 10, 2, 2), aggregated_pass_list[2]->damage_rect);
+    EXPECT_EQ(0u, aggregated_pass_list[0]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+    EXPECT_EQ(gfx::Rect(0, 0, 2, 2),
+              aggregated_pass_list[1]->quad_list.back()->visible_rect);
+    EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+
+    EXPECT_FALSE(aggregated_pass_list[0]->has_damage_from_contributing_content);
+    EXPECT_FALSE(aggregated_pass_list[1]->has_damage_from_contributing_content);
+  }
+
+  // New child frame has same content and no damage, but has a
+  // CopyOutputRequest.
+  {
+    CompositorRenderPassId child_pass_ids[] = {CompositorRenderPassId{1},
+                                               CompositorRenderPassId{2}};
+    std::vector<Quad> child_quads1 = {
+        Quad::SolidColorQuad({1.0, 0.0, 1.0, 1.0f / 255.0f}, gfx::Rect(5, 5))};
+    std::vector<Quad> child_quads2 = {
+        Quad::RenderPassQuad(child_pass_ids[0], gfx::Transform(), true)};
+    std::vector<Pass> child_passes = {
+        Pass(child_quads1, child_pass_ids[0], kSurfaceSize),
+        Pass(child_quads2, child_pass_ids[1], kSurfaceSize)};
+
+    CompositorRenderPassList child_pass_list;
+    std::vector<SurfaceRange> referenced_surfaces;
+    AddPasses(&child_pass_list, child_passes, &referenced_surfaces);
+
+    child_pass_list[0]->quad_list.ElementAt(0)->visible_rect =
+        gfx::Rect(1, 1, 2, 2);
+    auto* child_sqs = child_pass_list[0]->shared_quad_state_list.ElementAt(0u);
+    child_sqs->quad_to_target_transform.Translate(1, 1);
+    child_sqs->quad_to_target_transform.Scale(2, 2);
+
+    child_pass_list[1]->quad_list.ElementAt(0)->visible_rect =
+        gfx::Rect(0, 0, 2, 2);
+
+    auto* child_root_pass = child_pass_list[1].get();
+
+    child_root_pass->copy_requests.push_back(
+        CopyOutputRequest::CreateStubForTesting());
+    child_root_pass->damage_rect = gfx::Rect();
+
+    child_pass_list[0]->has_damage_from_contributing_content = false;
+    child_pass_list[1]->has_damage_from_contributing_content = false;
+
+    SubmitPassListAsFrame(child_sink_.get(),
+                          child_surface_id.local_surface_id(), &child_pass_list,
+                          std::move(referenced_surfaces), device_scale_factor);
+  }
+
+  {
+    auto aggregated_frame = AggregateFrame(root_surface_id_);
+
+    const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+    // Output frame should have no damage, but all quads included.
+    ASSERT_EQ(3u, aggregated_pass_list.size());
+
+    EXPECT_EQ(gfx::Rect(kSurfaceSize), aggregated_pass_list[0]->damage_rect);
+    EXPECT_EQ(gfx::Rect(kSurfaceSize), aggregated_pass_list[1]->damage_rect);
+    EXPECT_TRUE(aggregated_pass_list[2]->damage_rect.IsEmpty());
+    ASSERT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+    ASSERT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+    EXPECT_EQ(gfx::Rect(1, 1, 2, 2),
+              aggregated_pass_list[0]->quad_list.ElementAt(0)->visible_rect);
+    EXPECT_EQ(gfx::Rect(0, 0, 2, 2),
+              aggregated_pass_list[1]->quad_list.ElementAt(0)->visible_rect);
+    ASSERT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+
+    EXPECT_TRUE(aggregated_pass_list[1]->has_damage_from_contributing_content);
+    EXPECT_FALSE(aggregated_pass_list[2]->has_damage_from_contributing_content);
+  }
+
+  {
+    auto aggregated_frame = AggregateFrame(root_surface_id_);
+
+    const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+    // There were no changes since last aggregation, so output should be empty
+    // and have no damage.
+    ASSERT_EQ(1u, aggregated_pass_list.size());
+    EXPECT_TRUE(aggregated_pass_list[0]->damage_rect.IsEmpty());
+    ASSERT_EQ(0u, aggregated_pass_list[0]->quad_list.size());
+  }
+
+  // Reset and make full damage for all render passes.
+  {
+    CompositorRenderPassId root_pass_ids[] = {CompositorRenderPassId{1},
+                                              CompositorRenderPassId{2},
+                                              CompositorRenderPassId{3}};
+    std::vector<Quad> root_quads1 = {Quad::SurfaceQuad(
+        SurfaceRange(absl::nullopt, child_surface_id), SkColors::kWhite,
+        gfx::Rect(5, 5), /*stretch_content_to_fill_bounds=*/false)};
+    std::vector<Quad> root_quads2 = {
+        Quad::RenderPassQuad(root_pass_ids[0], gfx::Transform(), true)};
+    std::vector<Quad> root_quads3 = {
+        Quad::RenderPassQuad(root_pass_ids[1], gfx::Transform(), true)};
+    std::vector<Pass> root_passes = {
+        Pass(root_quads1, root_pass_ids[0], kSurfaceSize),
+        Pass(root_quads2, root_pass_ids[1], kSurfaceSize),
+        Pass(root_quads3, root_pass_ids[2], kSurfaceSize)};
+
+    CompositorRenderPassList root_pass_list;
+    std::vector<SurfaceRange> referenced_surfaces;
+    AddPasses(&root_pass_list, root_passes, &referenced_surfaces);
+
+    root_pass_list[0]->has_damage_from_contributing_content = true;
+    root_pass_list[1]->has_damage_from_contributing_content = true;
+    root_pass_list[2]->has_damage_from_contributing_content = true;
+    root_pass_list[0]->damage_rect = gfx::Rect(kSurfaceSize);
+    root_pass_list[1]->damage_rect = gfx::Rect(kSurfaceSize);
+    root_pass_list[2]->damage_rect = gfx::Rect(kSurfaceSize);
+
+    SubmitPassListAsFrame(root_sink_.get(), root_surface_id_.local_surface_id(),
+                          &root_pass_list, std::move(referenced_surfaces),
+                          device_scale_factor);
+
+    auto aggregated_frame = AggregateFrame(root_surface_id_);
+
+    const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+    ASSERT_EQ(4u, aggregated_pass_list.size());
+
+    EXPECT_EQ(gfx::Rect(kSurfaceSize), aggregated_pass_list[0]->damage_rect);
+    EXPECT_EQ(gfx::Rect(kSurfaceSize), aggregated_pass_list[1]->damage_rect);
+    EXPECT_EQ(gfx::Rect(kSurfaceSize), aggregated_pass_list[2]->damage_rect);
+    EXPECT_EQ(gfx::Rect(kSurfaceSize), aggregated_pass_list[3]->damage_rect);
+
+    EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[3]->quad_list.size());
+
+    // parent_clip_rect expands from
+    EXPECT_TRUE(aggregated_pass_list[0]->has_damage_from_contributing_content);
+    EXPECT_TRUE(aggregated_pass_list[1]->has_damage_from_contributing_content);
+    EXPECT_TRUE(aggregated_pass_list[2]->has_damage_from_contributing_content);
+  }
+
+  // Render passes with pixel-moving foreground filters will increase the damage
+  // only if the damage of the contents will overlap the expanded render pass
+  // draw quad. Since the root surface damage does not overlap, the render pass
+  // and its descendant passes should not be aggregated.
+  {
+    CompositorRenderPassId root_pass_ids[] = {CompositorRenderPassId{1},
+                                              CompositorRenderPassId{2},
+                                              CompositorRenderPassId{3}};
+    std::vector<Quad> root_quads1 = {Quad::SurfaceQuad(
+        SurfaceRange(absl::nullopt, child_surface_id), SkColors::kWhite,
+        gfx::Rect(5, 5), /*stretch_content_to_fill_bounds=*/false)};
+    std::vector<Quad> root_quads2 = {
+        Quad::RenderPassQuad(root_pass_ids[0], gfx::Transform(), true)};
+    std::vector<Quad> root_quads3 = {
+        Quad::RenderPassQuad(root_pass_ids[1], gfx::Transform(), true)};
+    std::vector<Pass> root_passes = {
+        Pass(root_quads1, root_pass_ids[0], kSurfaceSize),
+        Pass(root_quads2, root_pass_ids[1], kSurfaceSize),
+        Pass(root_quads3, root_pass_ids[2], kSurfaceSize)};
+
+    CompositorRenderPassList root_pass_list;
+    std::vector<SurfaceRange> referenced_surfaces;
+    AddPasses(&root_pass_list, root_passes, &referenced_surfaces);
+
+    auto* filter_pass = root_pass_list[1].get();
+    filter_pass->shared_quad_state_list.front()
+        ->quad_to_target_transform.Translate(10, 10);
+    // Create 3 pixel-moving filters with the same max pixel movement.
+    filter_pass->filters.Append(cc::FilterOperation::CreateBlurFilter(2));
+    filter_pass->filters.Append(cc::FilterOperation::CreateDropShadowFilter(
+        gfx::Point(0, 0), 2, SkColors::kTransparent));
+    filter_pass->filters.Append(cc::FilterOperation::CreateZoomFilter(2, 4));
+    auto* root_pass = root_pass_list[2].get();
+    // Set the root damage rect which doesn't intersect with the expanded
+    // filter_pass quad (-4, -4, 13, 13) (filter quad (0, 0, 5, 5) + blur filter
+    // pixel movement (2 * 3 = 6)), so we don't have to add more damage from the
+    // filter_pass and the first render pass draw quad will not be drawn.
+    root_pass->damage_rect = gfx::Rect(20, 20, 2, 2);
+    root_pass->has_damage_from_contributing_content = true;
+    root_pass_list[0]->has_damage_from_contributing_content = false;
+    root_pass_list[1]->has_damage_from_contributing_content = false;
+    root_pass_list[0]->damage_rect = gfx::Rect();
+    root_pass_list[1]->damage_rect = gfx::Rect();
+
+    SubmitPassListAsFrame(root_sink_.get(), root_surface_id_.local_surface_id(),
+                          &root_pass_list, std::move(referenced_surfaces),
+                          device_scale_factor);
+
+    auto aggregated_frame = AggregateFrame(root_surface_id_);
+
+    const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+    ASSERT_EQ(4u, aggregated_pass_list.size());
+
+    EXPECT_EQ(gfx::Rect(20, 20, 2, 2), aggregated_pass_list[0]->damage_rect);
+    EXPECT_EQ(gfx::Rect(20, 20, 2, 2), aggregated_pass_list[1]->damage_rect);
+    EXPECT_EQ(gfx::Rect(20, 20, 2, 2), aggregated_pass_list[2]->damage_rect);
+    // The filter pass does not intersects with the other damages. The root
+    // damage should not increase.
+    EXPECT_EQ(gfx::Rect(20, 20, 2, 2), aggregated_pass_list[3]->damage_rect);
+    EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+    // First render pass draw quad with filter is outside damage rect, so
+    // shouldn't be drawn.
+    EXPECT_EQ(0u, aggregated_pass_list[3]->quad_list.size());
+
+    EXPECT_FALSE(aggregated_pass_list[0]->has_damage_from_contributing_content);
+    EXPECT_FALSE(aggregated_pass_list[1]->has_damage_from_contributing_content);
+    EXPECT_FALSE(aggregated_pass_list[2]->has_damage_from_contributing_content);
+  }
+
+  // Render passes with pixel-moving foreground filters will increase the damage
+  // if the damage of the contents will overlap the expanded render pass draw
+  // quad (quad rect + maximum pixel movement). Since the root surface damage
+  // overlaps, the render pass and its descendant passes should be aggregated.
+  {
+    CompositorRenderPassId root_pass_ids[] = {CompositorRenderPassId{1},
+                                              CompositorRenderPassId{2},
+                                              CompositorRenderPassId{3}};
+    std::vector<Quad> root_quads1 = {Quad::SurfaceQuad(
+        SurfaceRange(absl::nullopt, child_surface_id), SkColors::kWhite,
+        gfx::Rect(5, 5), /*stretch_content_to_fill_bounds=*/false)};
+    std::vector<Quad> root_quads2 = {
+        Quad::RenderPassQuad(root_pass_ids[0], gfx::Transform(), true)};
+    std::vector<Quad> root_quads3 = {
+        Quad::RenderPassQuad(root_pass_ids[1], gfx::Transform(), true)};
+    std::vector<Pass> root_passes = {
+        Pass(root_quads1, root_pass_ids[0], kSurfaceSize),
+        Pass(root_quads2, root_pass_ids[1], kSurfaceSize),
+        Pass(root_quads3, root_pass_ids[2], kSurfaceSize)};
+
+    CompositorRenderPassList root_pass_list;
+    std::vector<SurfaceRange> referenced_surfaces;
+    AddPasses(&root_pass_list, root_passes, &referenced_surfaces);
+
+    auto* filter_pass = root_pass_list[1].get();
+    filter_pass->shared_quad_state_list.front()
+        ->quad_to_target_transform.Translate(10, 10);
+    // Create 3 pixel-moving filters with the same max pixel movement.
+    filter_pass->filters.Append(cc::FilterOperation::CreateBlurFilter(10));
+    filter_pass->filters.Append(cc::FilterOperation::CreateDropShadowFilter(
+        gfx::Point(0, 0), 10, SkColors::kTransparent));
+    filter_pass->filters.Append(cc::FilterOperation::CreateZoomFilter(2, 20));
+    auto* root_pass = root_pass_list[2].get();
+    // Make the root damage rect intersect with the expanded filter_pass quad
+    // (filter quad (0, 0, 5, 5) + blur filter pixel movement (10 * 3) = (-30,
+    // -30, 65, 65)), but not with filter_pass quad itself (0, 0, 5, 5). The
+    // first render pass will be drawn.
+    root_pass->damage_rect = gfx::Rect(20, 20, 2, 2);
+    root_pass->has_damage_from_contributing_content = true;
+    root_pass_list[0]->has_damage_from_contributing_content = false;
+    root_pass_list[1]->has_damage_from_contributing_content = false;
+
+    SubmitPassListAsFrame(root_sink_.get(), root_surface_id_.local_surface_id(),
+                          &root_pass_list, std::move(referenced_surfaces),
+                          device_scale_factor);
+
+    auto aggregated_frame = AggregateFrame(root_surface_id_);
+
+    const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+    ASSERT_EQ(4u, aggregated_pass_list.size());
+
+    EXPECT_EQ(gfx::Rect(0, 0, 35, 35), aggregated_pass_list[0]->damage_rect);
+    EXPECT_EQ(gfx::Rect(0, 0, 35, 35), aggregated_pass_list[1]->damage_rect);
+    EXPECT_EQ(gfx::Rect(0, 0, 35, 35), aggregated_pass_list[2]->damage_rect);
+    // The filter pass intersects with the root surface damage, the root damage
+    // should increase.
+    // damage_rect = original root damage (0, 0, 5, 5) + blur filter pixel
+    // movement (10 * 3) = (-30, -30, 65, 65). Then intersects with the root
+    // output_rect (0, 0, 100, 100) = (0, 0, 35, 35).
+    EXPECT_EQ(gfx::Rect(0, 0, 35, 35), aggregated_pass_list[3]->damage_rect);
+    EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+    // First render pass draw quad is damaged. It should be drawn.
+    EXPECT_EQ(1u, aggregated_pass_list[3]->quad_list.size());
+
+    EXPECT_FALSE(aggregated_pass_list[0]->has_damage_from_contributing_content);
+    EXPECT_FALSE(aggregated_pass_list[1]->has_damage_from_contributing_content);
+    EXPECT_FALSE(aggregated_pass_list[2]->has_damage_from_contributing_content);
+  }
+
+  // If child surface quad or child render pass output rect expands from the
+  // previous frame, the child render pass should redraw.
+  {
+    CompositorRenderPassId root_pass_ids[] = {CompositorRenderPassId{1},
+                                              CompositorRenderPassId{2},
+                                              CompositorRenderPassId{3}};
+    // Increase surface quad size from 5x5 to 30x30.
+    std::vector<Quad> root_quads1 = {Quad::SurfaceQuad(
+        SurfaceRange(absl::nullopt, child_surface_id), SkColors::kWhite,
+        gfx::Rect(30, 30), /*stretch_content_to_fill_bounds=*/false)};
+    std::vector<Quad> root_quads2 = {
+        Quad::RenderPassQuad(root_pass_ids[0], gfx::Transform(), true)};
+    std::vector<Quad> root_quads3 = {
+        Quad::RenderPassQuad(root_pass_ids[1], gfx::Transform(), true)};
+
+    // Expand the render pass [1] output rect size from 100x100 to 150x150.
+    std::vector<Pass> root_passes = {
+        Pass(root_quads1, root_pass_ids[0], kSurfaceSize),
+        Pass(root_quads2, root_pass_ids[1], gfx::Size(150, 150)),
+        Pass(root_quads3, root_pass_ids[2], kSurfaceSize)};
+
+    CompositorRenderPassList root_pass_list;
+    std::vector<SurfaceRange> referenced_surfaces;
+    AddPasses(&root_pass_list, root_passes, &referenced_surfaces);
+
+    // Make the root damage rect intersect with the child render passes.
+    auto* root_pass = root_pass_list[2].get();
+    root_pass->damage_rect = gfx::Rect(0, 0, 40, 40);
+    root_pass->has_damage_from_contributing_content = true;
+    root_pass_list[0]->has_damage_from_contributing_content = false;
+    root_pass_list[1]->has_damage_from_contributing_content = false;
+
+    SubmitPassListAsFrame(root_sink_.get(), root_surface_id_.local_surface_id(),
+                          &root_pass_list, std::move(referenced_surfaces),
+                          device_scale_factor);
+
+    auto aggregated_frame = AggregateFrame(root_surface_id_);
+
+    const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+    ASSERT_EQ(4u, aggregated_pass_list.size());
+
+    EXPECT_EQ(gfx::Rect(0, 0, 40, 40), aggregated_pass_list[0]->damage_rect);
+    EXPECT_EQ(gfx::Rect(0, 0, 40, 40), aggregated_pass_list[1]->damage_rect);
+    EXPECT_EQ(gfx::Rect(0, 0, 40, 40), aggregated_pass_list[2]->damage_rect);
+
+    EXPECT_EQ(gfx::Rect(0, 0, 40, 40), aggregated_pass_list[3]->damage_rect);
+    EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+    EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+    // First render pass draw quad is damaged. It should be drawn.
+    EXPECT_EQ(1u, aggregated_pass_list[3]->quad_list.size());
+
+    // SurfaceQuad |parent_clip_rect| expands from (0,0 5x5) to (0,0 30x30). It
+    // should redraw. |has_damage_from_contributing_content| should be true.
+    EXPECT_TRUE(aggregated_pass_list[0]->has_damage_from_contributing_content);
+    EXPECT_TRUE(aggregated_pass_list[1]->has_damage_from_contributing_content);
+
+    // RenderPassDrawQuad |parent_clip_rect| expands from (0,0 100x100) to (0,0
+    // 150x150). It should redraw. |has_damage_from_contributing_content| should
+    // be true.
+    EXPECT_TRUE(aggregated_pass_list[2]->has_damage_from_contributing_content);
+  }
+}
+
 TEST_F(SurfaceAggregatorPartialSwapTest, ExpandByTargetDamage) {
   TestSurfaceIdAllocator child_surface_id(child_sink_->frame_sink_id());
   constexpr float device_scale_factor = 1.0f;