// Copyright 2011 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/layers/render_surface_impl.h"

#include <stddef.h>

#include <algorithm>

#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "cc/base/math_util.h"
#include "cc/debug/debug_colors.h"
#include "cc/layers/append_quads_data.h"
#include "cc/paint/filter_operations.h"
#include "cc/trees/damage_tracker.h"
#include "cc/trees/draw_property_utils.h"
#include "cc/trees/effect_node.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/occlusion.h"
#include "cc/trees/transform_node.h"
#include "components/viz/common/quads/content_draw_quad_base.h"
#include "components/viz/common/quads/debug_border_draw_quad.h"
#include "components/viz/common/quads/render_pass.h"
#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"

namespace cc {

RenderSurfaceImpl::RenderSurfaceImpl(LayerTreeImpl* layer_tree_impl,
                                     uint64_t stable_id)
    : layer_tree_impl_(layer_tree_impl),
      stable_id_(stable_id),
      effect_tree_index_(EffectTree::kInvalidNodeId),
      num_contributors_(0),
      has_contributing_layer_that_escapes_clip_(false),
      surface_property_changed_(false),
      ancestor_property_changed_(false),
      contributes_to_drawn_surface_(false),
      is_render_surface_list_member_(false),
      nearest_occlusion_immune_ancestor_(nullptr) {
  damage_tracker_ = DamageTracker::Create();
}

RenderSurfaceImpl::~RenderSurfaceImpl() = default;

RenderSurfaceImpl* RenderSurfaceImpl::render_target() {
  EffectTree& effect_tree = layer_tree_impl_->property_trees()->effect_tree;
  EffectNode* node = effect_tree.Node(EffectTreeIndex());
  if (node->target_id != EffectTree::kRootNodeId)
    return effect_tree.GetRenderSurface(node->target_id);
  else
    return this;
}

const RenderSurfaceImpl* RenderSurfaceImpl::render_target() const {
  const EffectTree& effect_tree =
      layer_tree_impl_->property_trees()->effect_tree;
  const EffectNode* node = effect_tree.Node(EffectTreeIndex());
  if (node->target_id != EffectTree::kRootNodeId)
    return effect_tree.GetRenderSurface(node->target_id);
  else
    return this;
}

RenderSurfaceImpl::DrawProperties::DrawProperties() {
  draw_opacity = 1.f;
  is_clipped = false;
}

RenderSurfaceImpl::DrawProperties::~DrawProperties() = default;

gfx::RectF RenderSurfaceImpl::DrawableContentRect() const {
  if (content_rect().IsEmpty())
    return gfx::RectF();

  gfx::Rect surface_content_rect = content_rect();
  const FilterOperations& filters = Filters();
  if (!filters.IsEmpty()) {
    surface_content_rect =
        filters.MapRect(surface_content_rect, SurfaceScale().matrix());
  }
  gfx::RectF drawable_content_rect = MathUtil::MapClippedRect(
      draw_transform(), gfx::RectF(surface_content_rect));
  if (!filters.IsEmpty() && is_clipped()) {
    // Filter could move pixels around, but still need to be clipped.
    drawable_content_rect.Intersect(gfx::RectF(clip_rect()));
  }

  // If the rect has a NaN coordinate, we return empty rect to avoid crashes in
  // functions (for example, gfx::ToEnclosedRect) that are called on this rect.
  if (std::isnan(drawable_content_rect.x()) ||
      std::isnan(drawable_content_rect.y()) ||
      std::isnan(drawable_content_rect.right()) ||
      std::isnan(drawable_content_rect.bottom()))
    return gfx::RectF();

  return drawable_content_rect;
}

SkBlendMode RenderSurfaceImpl::BlendMode() const {
  return OwningEffectNode()->blend_mode;
}

bool RenderSurfaceImpl::UsesDefaultBlendMode() const {
  return BlendMode() == SkBlendMode::kSrcOver;
}

SkColor RenderSurfaceImpl::GetDebugBorderColor() const {
  return DebugColors::SurfaceBorderColor();
}

float RenderSurfaceImpl::GetDebugBorderWidth() const {
  return DebugColors::SurfaceBorderWidth(
      layer_tree_impl_ ? layer_tree_impl_->device_scale_factor() : 1);
}

LayerImpl* RenderSurfaceImpl::MaskLayer() {
  int mask_layer_id = OwningEffectNode()->mask_layer_id;
  return layer_tree_impl_->LayerById(mask_layer_id);
}

bool RenderSurfaceImpl::HasMask() const {
  return OwningEffectNode()->mask_layer_id != Layer::INVALID_ID;
}

bool RenderSurfaceImpl::HasMaskingContributingSurface() const {
  return OwningEffectNode()->has_masking_child;
}

const FilterOperations& RenderSurfaceImpl::Filters() const {
  return OwningEffectNode()->filters;
}

gfx::PointF RenderSurfaceImpl::FiltersOrigin() const {
  return OwningEffectNode()->filters_origin;
}

gfx::Transform RenderSurfaceImpl::SurfaceScale() const {
  gfx::Transform surface_scale;
  surface_scale.Scale(OwningEffectNode()->surface_contents_scale.x(),
                      OwningEffectNode()->surface_contents_scale.y());
  return surface_scale;
}

const FilterOperations& RenderSurfaceImpl::BackdropFilters() const {
  return OwningEffectNode()->backdrop_filters;
}

const gfx::RectF& RenderSurfaceImpl::BackdropFilterBounds() const {
  return OwningEffectNode()->backdrop_filter_bounds;
}

bool RenderSurfaceImpl::TrilinearFiltering() const {
  return OwningEffectNode()->trilinear_filtering;
}

bool RenderSurfaceImpl::HasCopyRequest() const {
  return OwningEffectNode()->has_copy_request;
}

bool RenderSurfaceImpl::ShouldCacheRenderSurface() const {
  return OwningEffectNode()->cache_render_surface;
}

int RenderSurfaceImpl::TransformTreeIndex() const {
  return OwningEffectNode()->transform_id;
}

int RenderSurfaceImpl::ClipTreeIndex() const {
  return OwningEffectNode()->clip_id;
}

int RenderSurfaceImpl::EffectTreeIndex() const {
  return effect_tree_index_;
}

const EffectNode* RenderSurfaceImpl::OwningEffectNode() const {
  return layer_tree_impl_->property_trees()->effect_tree.Node(
      EffectTreeIndex());
}

void RenderSurfaceImpl::SetClipRect(const gfx::Rect& clip_rect) {
  if (clip_rect == draw_properties_.clip_rect)
    return;

  surface_property_changed_ = true;
  draw_properties_.clip_rect = clip_rect;
}

void RenderSurfaceImpl::SetContentRect(const gfx::Rect& content_rect) {
  if (content_rect == draw_properties_.content_rect)
    return;

  surface_property_changed_ = true;
  draw_properties_.content_rect = content_rect;
}

void RenderSurfaceImpl::SetContentRectForTesting(const gfx::Rect& rect) {
  SetContentRect(rect);
}

gfx::Rect RenderSurfaceImpl::CalculateExpandedClipForFilters(
    const gfx::Transform& target_to_surface) {
  gfx::Rect clip_in_surface_space =
      MathUtil::ProjectEnclosingClippedRect(target_to_surface, clip_rect());
  gfx::Rect expanded_clip_in_surface_space =
      Filters().MapRectReverse(clip_in_surface_space, SurfaceScale().matrix());
  gfx::Rect expanded_clip_in_target_space = MathUtil::MapEnclosingClippedRect(
      draw_transform(), expanded_clip_in_surface_space);
  return expanded_clip_in_target_space;
}

gfx::Rect RenderSurfaceImpl::CalculateClippedAccumulatedContentRect() {
  if (ShouldCacheRenderSurface() || HasCopyRequest() || !is_clipped())
    return accumulated_content_rect();

  if (accumulated_content_rect().IsEmpty())
    return gfx::Rect();

  // Calculate projection from the target surface rect to local
  // space. Non-invertible draw transforms means no able to bring clipped rect
  // in target space back to local space, early out without clip.
  gfx::Transform target_to_surface(gfx::Transform::kSkipInitialization);
  if (!draw_transform().GetInverse(&target_to_surface))
    return accumulated_content_rect();

  // Clip rect is in target space. Bring accumulated content rect to
  // target space in preparation for clipping.
  gfx::Rect accumulated_rect_in_target_space =
      MathUtil::MapEnclosingClippedRect(draw_transform(),
                                        accumulated_content_rect());
  // If accumulated content rect is contained within clip rect, early out
  // without clipping.
  if (clip_rect().Contains(accumulated_rect_in_target_space))
    return accumulated_content_rect();

  gfx::Rect clipped_accumulated_rect_in_target_space;
  if (Filters().HasFilterThatMovesPixels()) {
    clipped_accumulated_rect_in_target_space =
        CalculateExpandedClipForFilters(target_to_surface);
  } else {
    clipped_accumulated_rect_in_target_space = clip_rect();
  }
  clipped_accumulated_rect_in_target_space.Intersect(
      accumulated_rect_in_target_space);

  if (clipped_accumulated_rect_in_target_space.IsEmpty())
    return gfx::Rect();

  gfx::Rect clipped_accumulated_rect_in_local_space =
      MathUtil::ProjectEnclosingClippedRect(
          target_to_surface, clipped_accumulated_rect_in_target_space);
  // Bringing clipped accumulated rect back to local space may result
  // in inflation due to axis-alignment.
  clipped_accumulated_rect_in_local_space.Intersect(accumulated_content_rect());
  return clipped_accumulated_rect_in_local_space;
}

void RenderSurfaceImpl::CalculateContentRectFromAccumulatedContentRect(
    int max_texture_size) {
  // Root render surface use viewport, and does not calculate content rect.
  DCHECK_NE(render_target(), this);

  // Surface's content rect is the clipped accumulated content rect. By default
  // 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.
  surface_content_rect.set_width(
      std::min(surface_content_rect.width(), max_texture_size));
  surface_content_rect.set_height(
      std::min(surface_content_rect.height(), max_texture_size));

  SetContentRect(surface_content_rect);
}

void RenderSurfaceImpl::SetContentRectToViewport() {
  // Only root render surface use viewport as content rect.
  DCHECK_EQ(render_target(), this);
  gfx::Rect viewport = gfx::ToEnclosingRect(
      layer_tree_impl_->property_trees()->clip_tree.ViewportClip());
  SetContentRect(viewport);
}

void RenderSurfaceImpl::ClearAccumulatedContentRect() {
  accumulated_content_rect_ = gfx::Rect();
}

void RenderSurfaceImpl::AccumulateContentRectFromContributingLayer(
    LayerImpl* layer) {
  DCHECK(layer->DrawsContent());
  DCHECK_EQ(this, layer->render_target());

  // Root render surface doesn't accumulate content rect, it always uses
  // viewport for content rect.
  if (render_target() == this)
    return;

  accumulated_content_rect_.Union(layer->drawable_content_rect());
}

void RenderSurfaceImpl::AccumulateContentRectFromContributingRenderSurface(
    RenderSurfaceImpl* contributing_surface) {
  DCHECK_NE(this, contributing_surface);
  DCHECK_EQ(this, contributing_surface->render_target());

  // Root render surface doesn't accumulate content rect, it always uses
  // viewport for content rect.
  if (render_target() == this)
    return;

  // The content rect of contributing surface is in its own space. Instead, we
  // will use contributing surface's DrawableContentRect which is in target
  // space (local space for this render surface) as required.
  accumulated_content_rect_.Union(
      gfx::ToEnclosedRect(contributing_surface->DrawableContentRect()));
}

bool RenderSurfaceImpl::SurfacePropertyChanged() const {
  // Surface property changes are tracked as follows:
  //
  // - surface_property_changed_ is flagged when the clip_rect or content_rect
  //   change. As of now, these are the only two properties that can be affected
  //   by descendant layers.
  //
  // - all other property changes come from the surface's property tree nodes
  //   (or some ancestor node that propagates its change to one of these nodes).
  //
  return surface_property_changed_ || AncestorPropertyChanged();
}

bool RenderSurfaceImpl::SurfacePropertyChangedOnlyFromDescendant() const {
  return surface_property_changed_ && !AncestorPropertyChanged();
}

bool RenderSurfaceImpl::AncestorPropertyChanged() const {
  const PropertyTrees* property_trees = layer_tree_impl_->property_trees();
  return ancestor_property_changed_ || property_trees->full_tree_damaged ||
         property_trees->transform_tree.Node(TransformTreeIndex())
             ->transform_changed ||
         property_trees->effect_tree.Node(EffectTreeIndex())->effect_changed;
}

void RenderSurfaceImpl::NoteAncestorPropertyChanged() {
  ancestor_property_changed_ = true;
}

bool RenderSurfaceImpl::HasDamageFromeContributingContent() const {
  return damage_tracker_->has_damage_from_contributing_content();
}

gfx::Rect RenderSurfaceImpl::GetDamageRect() const {
  gfx::Rect damage_rect;
  bool is_valid_rect = damage_tracker_->GetDamageRectIfValid(&damage_rect);
  if (!is_valid_rect)
    return content_rect();
  return damage_rect;
}

void RenderSurfaceImpl::ResetPropertyChangedFlags() {
  surface_property_changed_ = false;
  ancestor_property_changed_ = false;
}

std::unique_ptr<viz::RenderPass> RenderSurfaceImpl::CreateRenderPass() {
  std::unique_ptr<viz::RenderPass> pass =
      viz::RenderPass::Create(num_contributors_);
  gfx::Rect damage_rect = GetDamageRect();
  damage_rect.Intersect(content_rect());
  pass->SetNew(id(), content_rect(), damage_rect,
               draw_properties_.screen_space_transform);
  pass->filters = Filters();
  pass->backdrop_filters = BackdropFilters();
  pass->backdrop_filter_bounds = BackdropFilterBounds();
  pass->generate_mipmap = TrilinearFiltering();
  pass->cache_render_pass = ShouldCacheRenderSurface();
  pass->has_damage_from_contributing_content =
      HasDamageFromeContributingContent();
  return pass;
}

void RenderSurfaceImpl::AppendQuads(DrawMode draw_mode,
                                    viz::RenderPass* render_pass,
                                    AppendQuadsData* append_quads_data) {
  gfx::Rect unoccluded_content_rect =
      occlusion_in_content_space().GetUnoccludedContentRect(content_rect());
  if (unoccluded_content_rect.IsEmpty())
    return;

  const PropertyTrees* property_trees = layer_tree_impl_->property_trees();
  int sorting_context_id =
      property_trees->transform_tree.Node(TransformTreeIndex())
          ->sorting_context_id;
  bool contents_opaque = false;
  viz::SharedQuadState* shared_quad_state =
      render_pass->CreateAndAppendSharedQuadState();
  shared_quad_state->SetAll(
      draw_transform(), content_rect(), content_rect(),
      draw_properties_.clip_rect, draw_properties_.is_clipped, contents_opaque,
      draw_properties_.draw_opacity, BlendMode(), sorting_context_id);

  if (layer_tree_impl_->debug_state().show_debug_borders.test(
          DebugBorderType::RENDERPASS)) {
    auto* debug_border_quad =
        render_pass->CreateAndAppendDrawQuad<viz::DebugBorderDrawQuad>();
    debug_border_quad->SetNew(shared_quad_state, content_rect(),
                              unoccluded_content_rect, GetDebugBorderColor(),
                              GetDebugBorderWidth());
  }

  viz::ResourceId mask_resource_id = 0;
  gfx::Size mask_texture_size;
  gfx::RectF mask_uv_rect;
  gfx::Vector2dF surface_contents_scale =
      OwningEffectNode()->surface_contents_scale;
  PictureLayerImpl* mask_layer = static_cast<PictureLayerImpl*>(MaskLayer());
  // Resourceless mode does not support masks.
  if (draw_mode != DRAW_MODE_RESOURCELESS_SOFTWARE && mask_layer &&
      mask_layer->DrawsContent() && !mask_layer->bounds().IsEmpty()) {
    // The software renderer applies mask layer and blending in the wrong
    // order but kDstIn doesn't commute with masking. It is okay to not
    // support this configuration because kDstIn was introduced to replace
    // mask layers.
    DCHECK(BlendMode() != SkBlendMode::kDstIn)
        << "kDstIn blend mode with mask layer is unsupported.";
    TRACE_EVENT1("cc", "RenderSurfaceImpl::AppendQuads",
                 "mask_layer_gpu_memory_usage",
                 mask_layer->GPUMemoryUsageInBytes());

    if (mask_layer->mask_type() == Layer::LayerMaskType::MULTI_TEXTURE_MASK) {
      TileMaskLayer(render_pass, shared_quad_state, unoccluded_content_rect);
      return;
    }
    gfx::SizeF mask_uv_size;
    mask_layer->GetContentsResourceId(&mask_resource_id, &mask_texture_size,
                                      &mask_uv_size);
    gfx::SizeF unclipped_mask_target_size = gfx::ScaleSize(
        gfx::SizeF(OwningEffectNode()->unscaled_mask_target_size),
        surface_contents_scale.x(), surface_contents_scale.y());
    // Convert content_rect from target space to normalized mask UV space.
    // Where |unclipped_mask_target_size| maps to |mask_uv_size|.
    mask_uv_rect = gfx::ScaleRect(
        gfx::RectF(content_rect()),
        mask_uv_size.width() / unclipped_mask_target_size.width(),
        mask_uv_size.height() / unclipped_mask_target_size.height());
  }

  gfx::RectF tex_coord_rect(gfx::Rect(content_rect().size()));
  auto* quad = render_pass->CreateAndAppendDrawQuad<viz::RenderPassDrawQuad>();
  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,
               OwningEffectNode()->backdrop_filter_quality);
}

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);

  auto* temp_quad = temp_render_pass->quad_list.front();
  if (!temp_quad)
    return;

  // 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;
  // 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 =
      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 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;

    // 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;

    // |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());

    constexpr float backdrop_filter_quality = 1.0;
    switch (temp_quad->material) {
      case viz::DrawQuad::TILED_CONTENT: {
        DCHECK_EQ(1U, temp_quad->resources.count);
        // 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 =
            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, quad_rect, visible_quad_rect, id(),
                     temp_quad->resources.ids[0], mask_uv_rect,
                     mask_texture_size, owning_layer_to_surface_contents_scale,
                     FiltersOrigin(), tex_coord_rect,
                     !layer_tree_impl_->settings().enable_edge_anti_aliasing,
                     backdrop_filter_quality);
      } break;
      case viz::DrawQuad::SOLID_COLOR: {
        SkColor temp_color =
            viz::SolidColorDrawQuad::MaterialCast(temp_quad)->color;
        // Check the alpha channel of the color. We apply the mask by
        // multiplying with the alpha channel, so if the alpha channel is
        // transparent, we skip this quad.
        if (SkColorGetA(temp_color) == SK_AlphaTRANSPARENT)
          continue;
        SkAlpha solid = SK_AlphaOPAQUE;
        DCHECK_EQ(SkColorGetA(temp_color), solid);

        auto* quad =
            render_pass->CreateAndAppendDrawQuad<viz::RenderPassDrawQuad>();
        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,
                     backdrop_filter_quality);
      } break;
      case viz::DrawQuad::DEBUG_BORDER:
        NOTIMPLEMENTED();
        break;
      default:
        NOTREACHED();
        break;
    }
  }
}

}  // namespace cc
