chromium/components/viz/service/display/ca_layer_overlay.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/service/display/ca_layer_overlay.h"

#include <algorithm>
#include <limits>

#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "components/viz/common/features.h"
#include "components/viz/common/quads/aggregated_render_pass_draw_quad.h"
#include "components/viz/common/quads/draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/quads/tile_draw_quad.h"
#include "components/viz/common/quads/yuv_video_draw_quad.h"
#include "components/viz/service/display/display_resource_provider.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "ui/base/cocoa/remote_layer_api.h"
#include "ui/gfx/buffer_types.h"

namespace viz {

namespace {

// The CoreAnimation renderer's performance starts suffering when too many
// quads are promoted to CALayers. At extremes, corruption can occur.
// https://crbug.com/1022116

// The default CALayer number allowed for CoreAnimation when kCALayerNewLimit is
// disabled.
constexpr size_t kLayerLimitDefault = 128;

// The new limit if kCALayerNewLimit is enabled. It can be overridden by the
// "default" feature parameters.
constexpr size_t kLayerNewLimitDefault = 1024;

// The default CALayer number allowed for CoreAnimation with many videos (video
// count >= kMaxNumVideos) when kCALayerNewLimit is disabled.
constexpr size_t kLayerLimitWithManyVideos = 300;

// The new limit with many videos if kCALayerNewLimit is enabled. It can be
// overridden by the "many-video" feature parameters.
constexpr size_t kLayerNewLimitWithManyVideos = 1024;

// If there are too many RenderPassDrawQuads, we shouldn't use Core
// Animation to present them as individual layers, since that potentially
// doubles the amount of work needed to present them. cc has to blit them into
// an IOSurface, and then Core Animation has to blit them to the final surface.
// https://crbug.com/636884.
const int kTooManyRenderPassDrawQuads = 30;

// Assume we are in a video conference if the total video count is bigger than
// or equal to this number.
const int kMaxNumVideos = 5;

void RecordCALayerHistogram(gfx::CALayerResult result) {
  UMA_HISTOGRAM_ENUMERATION("Compositing.Renderer.CALayerResult", result);
}

bool FilterOperationSupported(const cc::FilterOperation& operation) {
  switch (operation.type()) {
    case cc::FilterOperation::GRAYSCALE:
    case cc::FilterOperation::SEPIA:
    case cc::FilterOperation::SATURATE:
    case cc::FilterOperation::HUE_ROTATE:
    case cc::FilterOperation::INVERT:
    case cc::FilterOperation::BRIGHTNESS:
    case cc::FilterOperation::CONTRAST:
    case cc::FilterOperation::OPACITY:
    case cc::FilterOperation::BLUR:
    case cc::FilterOperation::DROP_SHADOW:
      return true;
    default:
      return false;
  }
}

gfx::CALayerResult FromRenderPassQuad(
    const DisplayResourceProvider* resource_provider,
    const AggregatedRenderPassDrawQuad* quad,
    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
        render_pass_filters,
    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
        render_pass_backdrop_filters,
    OverlayCandidate* ca_layer_overlay) {
  if (render_pass_backdrop_filters.count(quad->render_pass_id)) {
    return gfx::kCALayerFailedRenderPassBackdropFilters;
  }

  auto* shared_quad_state = quad->shared_quad_state;
  if (shared_quad_state->sorting_context_id != 0)
    return gfx::kCALayerFailedRenderPassSortingContextId;

  auto it = render_pass_filters.find(quad->render_pass_id);
  if (it != render_pass_filters.end()) {
    for (const auto& operation : it->second->operations()) {
      bool success = FilterOperationSupported(operation);
      if (!success)
        return gfx::kCALayerFailedRenderPassFilterOperation;
    }
  }

  // TODO(crbug.com/40769959): support not 2d axis aligned clipping.
  if (shared_quad_state->clip_rect &&
      !shared_quad_state->quad_to_target_transform.Preserves2dAxisAlignment()) {
    return gfx::kCALayerFailedQuadClipping;
  }

  // TODO(crbug.com/40769959): support not 2d axis aligned mask.
  if (!shared_quad_state->mask_filter_info.IsEmpty() &&
      !shared_quad_state->quad_to_target_transform.Preserves2dAxisAlignment()) {
    return gfx::kCALayerFailedRenderPassPassMask;
  }

  ca_layer_overlay->rpdq = quad;
  ca_layer_overlay->is_render_pass_draw_quad = true;
  ca_layer_overlay->uv_rect = gfx::RectF(0, 0, 1, 1);

  // For RenderPassDrawQuad, the opacity is applied when its ddl is recorded, so
  // the content already is with opacity applied.
  ca_layer_overlay->opacity = 1.0;

  return gfx::kCALayerSuccess;
}

gfx::CALayerResult FromSolidColorDrawQuad(const SolidColorDrawQuad* quad,
                                          OverlayCandidate* ca_layer_overlay,
                                          bool* skip) {
  // Do not generate quads that are completely transparent.
  if (quad->color.fA == 0.0f) {
    *skip = true;
    return gfx::kCALayerSuccess;
  }
  ca_layer_overlay->color = quad->color;
  ca_layer_overlay->is_solid_color = true;
  return gfx::kCALayerSuccess;
}

gfx::CALayerResult FromTextureQuad(
    const DisplayResourceProvider* resource_provider,
    const TextureDrawQuad* quad,
    OverlayCandidate* ca_layer_overlay) {
  ResourceId resource_id = quad->resource_id();
  if (!resource_provider->IsOverlayCandidate(resource_id))
    return gfx::kCALayerFailedTextureNotCandidate;
  if (quad->y_flipped) {
    auto transform = absl::get<gfx::Transform>(ca_layer_overlay->transform);
    // The anchor point is at the bottom-left corner of the CALayer. The
    // transformation that flips the contents of the layer without changing its
    // frame is the composition of a vertical flip about the anchor point, and a
    // translation by the height of the layer.
    transform.Translate(0, ca_layer_overlay->display_rect.height());
    transform.Scale(1, -1);
    ca_layer_overlay->transform = transform;
  }
  ca_layer_overlay->resource_id = resource_id;
  ca_layer_overlay->uv_rect =
      BoundingRect(quad->uv_top_left, quad->uv_bottom_right);
  ca_layer_overlay->color = quad->background_color;
  ca_layer_overlay->nearest_neighbor_filter = quad->nearest_neighbor;
  ca_layer_overlay->hdr_metadata =
      resource_provider->GetHDRMetadata(resource_id);
  if (quad->is_video_frame)
    ca_layer_overlay->protected_video_type = quad->protected_video_type;
  return gfx::kCALayerSuccess;
}

gfx::CALayerResult FromYUVVideoQuad(
    const DisplayResourceProvider* resource_provider,
    const YUVVideoDrawQuad* quad,
    OverlayCandidate* ca_layer_overlay,
    bool& video_with_odd_width_out,
    bool& video_with_odd_height_out,
    bool& video_with_odd_x_out,
    bool& video_with_odd_y_out) {
  // For YUVVideoDrawQuads, the Y and UV planes alias the same underlying
  // IOSurface. Ensure all planes are overlays and have the same contents
  // rect. Then use the Y plane as the resource for the overlay.
  ResourceId y_resource_id = quad->y_plane_resource_id();
  if (!resource_provider->IsOverlayCandidate(y_resource_id) ||
      !resource_provider->IsOverlayCandidate(quad->u_plane_resource_id()) ||
      !resource_provider->IsOverlayCandidate(quad->v_plane_resource_id())) {
    return gfx::kCALayerFailedYUVNotCandidate;
  }

  if (quad->y_plane_resource_id() == quad->u_plane_resource_id() ||
      quad->y_plane_resource_id() == quad->v_plane_resource_id() ||
      quad->u_plane_resource_id() != quad->v_plane_resource_id()) {
    return gfx::kCALayerFailedYUVInvalidPlanes;
  }

  // Use division to calculate |ya_contents_rect| instead of using
  // gfx::ScaleRect (which would multiply by the reciprocal), to avoid
  // introducing excessive floating-point errors.
  gfx::RectF ya_contents_rect = {
      (quad->ya_tex_coord_rect().x() / quad->ya_tex_size().width()),
      (quad->ya_tex_coord_rect().y() / quad->ya_tex_size().height()),
      (quad->ya_tex_coord_rect().width() / quad->ya_tex_size().width()),
      (quad->ya_tex_coord_rect().height() / quad->ya_tex_size().height())};
  gfx::RectF uv_contents_rect = {
      (quad->uv_tex_coord_rect().x() / quad->uv_tex_size().width()),
      (quad->uv_tex_coord_rect().y() / quad->uv_tex_size().height()),
      (quad->uv_tex_coord_rect().width() / quad->uv_tex_size().width()),
      (quad->uv_tex_coord_rect().height() / quad->uv_tex_size().height())};
  // For odd-sized videos, |ya_tex_coord_rect| and |uv_tex_coord_rect| might not
  // be identical.
  float tolerance_x = 1.5f / quad->uv_tex_size().width();
  float tolerance_y = 1.5f / quad->uv_tex_size().height();
  if (!ya_contents_rect.ApproximatelyEqual(uv_contents_rect, tolerance_x,
                                           tolerance_y)) {
    return gfx::kCALayerFailedYUVTexcoordMismatch;
  }

  // Check any odd sized and odd offset video in the current frame.
  if (quad->ya_tex_size().width() % 2)
    video_with_odd_width_out = true;
  if (quad->ya_tex_size().height() % 2)
    video_with_odd_height_out = true;
  float integer = 0;
  if (std::modf(quad->ya_tex_coord_rect().x() / 2.f, &integer) != 0)
    video_with_odd_x_out = true;
  if (std::modf(quad->ya_tex_coord_rect().y() / 2.f, &integer) != 0)
    video_with_odd_y_out = true;

  ca_layer_overlay->resource_id = y_resource_id;
  ca_layer_overlay->uv_rect = ya_contents_rect;
  ca_layer_overlay->hdr_metadata =
      quad->hdr_metadata.value_or(gfx::HDRMetadata());
  ca_layer_overlay->protected_video_type = quad->protected_video_type;
  return gfx::kCALayerSuccess;
}

gfx::CALayerResult FromTileQuad(
    const DisplayResourceProvider* resource_provider,
    const TileDrawQuad* quad,
    OverlayCandidate* ca_layer_overlay) {
  ResourceId resource_id = quad->resource_id();
  if (!resource_provider->IsOverlayCandidate(resource_id))
    return gfx::kCALayerFailedTileNotCandidate;
  ca_layer_overlay->resource_id = resource_id;
  ca_layer_overlay->uv_rect = quad->tex_coord_rect;
  ca_layer_overlay->uv_rect.InvScale(quad->texture_size.width(),
                                     quad->texture_size.height());
  ca_layer_overlay->nearest_neighbor_filter = quad->nearest_neighbor;
  return gfx::kCALayerSuccess;
}

class CALayerOverlayProcessorInternal {
 public:
  gfx::CALayerResult FromDrawQuad(
      const DisplayResourceProvider* resource_provider,
      const gfx::RectF& display_rect,
      const DrawQuad* quad,
      const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
          render_pass_filters,
      const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
          render_pass_backdrop_filters,
      OverlayCandidate* ca_layer_overlay,
      bool* skip,
      bool* render_pass_draw_quad,
      int& yuv_draw_quad_count) {
    if (quad->shared_quad_state->blend_mode != SkBlendMode::kSrcOver)
      return gfx::kCALayerFailedQuadBlendMode;

    // Early-out for invisible quads.
    if (quad->shared_quad_state->opacity == 0.f ||
        quad->visible_rect.IsEmpty()) {
      *skip = true;
      return gfx::kCALayerSuccess;
    }

    // Support rounded corner bounds when they have the same rect as the clip
    // rect, and all corners have the same radius. Note that it is entirely
    // possible to make rounded corner rects independent of clip rect (by adding
    // another CALayer to the tree). Handling non-single border radii is also,
    // but requires APIs not supported on all macOS versions.
    if (quad->shared_quad_state->mask_filter_info.HasRoundedCorners()) {
      if (quad->shared_quad_state->mask_filter_info.rounded_corner_bounds()
              .GetType() > gfx::RRectF::Type::kSingle) {
        return gfx::kCALayerFailedQuadRoundedCornerNotUniform;
      }
    }

    // Enable edge anti-aliasing only on layer boundaries.
    ca_layer_overlay->edge_aa_mask = 0;
    if (quad->IsLeftEdge())
      ca_layer_overlay->edge_aa_mask |= ui::CALayerEdge::kLayerEdgeLeft;
    if (quad->IsRightEdge())
      ca_layer_overlay->edge_aa_mask |= ui::CALayerEdge::kLayerEdgeRight;
    if (quad->IsBottomEdge())
      ca_layer_overlay->edge_aa_mask |= ui::CALayerEdge::kLayerEdgeBottom;
    if (quad->IsTopEdge())
      ca_layer_overlay->edge_aa_mask |= ui::CALayerEdge::kLayerEdgeTop;

    ca_layer_overlay->sorting_context_id =
        quad->shared_quad_state->sorting_context_id;
    ca_layer_overlay->clip_rect = quad->shared_quad_state->clip_rect;
    ca_layer_overlay->rounded_corners =
        quad->shared_quad_state->mask_filter_info.rounded_corner_bounds();
    ca_layer_overlay->transform =
        quad->shared_quad_state->quad_to_target_transform;

    ca_layer_overlay->display_rect = gfx::RectF(quad->rect);
    ca_layer_overlay->opacity = quad->shared_quad_state->opacity;

    *render_pass_draw_quad =
        quad->material == DrawQuad::Material::kAggregatedRenderPass;
    switch (quad->material) {
      case DrawQuad::Material::kTextureContent: {
        const TextureDrawQuad* texture_draw_quad =
            TextureDrawQuad::MaterialCast(quad);
        // Stream video and video frame counts as a yuv draw quad.
        if (texture_draw_quad->is_stream_video ||
            texture_draw_quad->is_video_frame) {
          yuv_draw_quad_count += 1;
        }
        return FromTextureQuad(resource_provider, texture_draw_quad,
                               ca_layer_overlay);
      }
      case DrawQuad::Material::kTiledContent:
        return FromTileQuad(resource_provider, TileDrawQuad::MaterialCast(quad),
                            ca_layer_overlay);
      case DrawQuad::Material::kSolidColor:
        return FromSolidColorDrawQuad(SolidColorDrawQuad::MaterialCast(quad),
                                      ca_layer_overlay, skip);
      case DrawQuad::Material::kDebugBorder:
        return gfx::kCALayerFailedDebugBoarder;
      case DrawQuad::Material::kPictureContent:
        return gfx::kCALayerFailedPictureContent;
      case DrawQuad::Material::kAggregatedRenderPass:
        return FromRenderPassQuad(
            resource_provider, AggregatedRenderPassDrawQuad::MaterialCast(quad),
            render_pass_filters, render_pass_backdrop_filters,
            ca_layer_overlay);
      case DrawQuad::Material::kSurfaceContent:
        return gfx::kCALayerFailedSurfaceContent;
      case DrawQuad::Material::kYuvVideoContent:
        yuv_draw_quad_count++;
        return FromYUVVideoQuad(
            resource_provider, YUVVideoDrawQuad::MaterialCast(quad),
            ca_layer_overlay, video_with_odd_width_, video_with_odd_height_,
            video_with_odd_x_, video_with_odd_y_);
      default:
        break;
    }

    return gfx::kCALayerFailedUnknown;
  }

  bool video_with_odd_width() { return video_with_odd_width_; }
  bool video_with_odd_height() { return video_with_odd_height_; }
  bool video_with_odd_x() { return video_with_odd_x_; }
  bool video_with_odd_y() { return video_with_odd_y_; }

 private:
  bool video_with_odd_width_ = false;
  bool video_with_odd_height_ = false;
  bool video_with_odd_x_ = false;
  bool video_with_odd_y_ = false;
};

// Control using the CoreAnimation renderer, which is the path that replaces
// all quads with CALayers.
BASE_FEATURE(kCARenderer,
             "CoreAnimationRenderer",
             base::FEATURE_ENABLED_BY_DEFAULT);

// Control using the CoreAnimation renderer, which is the path that replaces
// all quads with CALayers.
BASE_FEATURE(kHDRUnderlays,
             "CoreAnimationHDRUnderlays",
             base::FEATURE_ENABLED_BY_DEFAULT);

}  // namespace

CALayerOverlayProcessor::CALayerOverlayProcessor()
    : overlays_allowed_(ui::RemoteLayerAPISupported()),
      enable_ca_renderer_(base::FeatureList::IsEnabled(kCARenderer)),
      enable_hdr_underlays_(base::FeatureList::IsEnabled(kHDRUnderlays)) {
  layer_limit_default_ = kLayerLimitDefault;
  layer_limit_with_many_videos_ = kLayerLimitWithManyVideos;
  if (base::FeatureList::IsEnabled(features::kCALayerNewLimit)) {
    layer_limit_default_ = kLayerNewLimitDefault;
    const int layer_limit_default_field_trial =
        features::kCALayerNewLimitDefault.Get();
    if (layer_limit_default_field_trial > 0) {
      layer_limit_default_ = layer_limit_default_field_trial;
    }

    const int layer_limit_with_many_videos_field_trial =
        features::kCALayerNewLimitManyVideos.Get();
    layer_limit_with_many_videos_ = kLayerNewLimitWithManyVideos;
    if (layer_limit_with_many_videos_field_trial > 0) {
      layer_limit_with_many_videos_ = layer_limit_with_many_videos_field_trial;
    }
  }
}

bool CALayerOverlayProcessor::AreClipSettingsValid(
    const OverlayCandidate& ca_layer_overlay,
    OverlayCandidateList* ca_layer_overlay_list) const {
  // It is not possible to correctly represent two different clipping
  // settings within one sorting context.
  if (!ca_layer_overlay_list->empty()) {
    const OverlayCandidate& previous_ca_layer = ca_layer_overlay_list->back();
    if (ca_layer_overlay.sorting_context_id &&
        previous_ca_layer.sorting_context_id ==
            ca_layer_overlay.sorting_context_id) {
      if (previous_ca_layer.clip_rect != ca_layer_overlay.clip_rect) {
        return false;
      }
    }
  }

  return true;
}

void CALayerOverlayProcessor::PutForcedOverlayContentIntoUnderlays(
    const DisplayResourceProvider* resource_provider,
    AggregatedRenderPass* render_pass,
    const gfx::RectF& display_rect,
    QuadList* quad_list,
    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
        render_pass_filters,
    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
        render_pass_backdrop_filters,
    OverlayCandidateList* ca_layer_overlays) const {
  bool failed = false;

  for (auto it = quad_list->begin(); it != quad_list->end(); ++it) {
    const DrawQuad* quad = *it;
    bool force_quad_to_overlay = false;
    gfx::ProtectedVideoType protected_video_type =
        gfx::ProtectedVideoType::kClear;

    // Put hardware protected video into an overlay
    if (quad->material == ContentDrawQuadBase::Material::kYuvVideoContent) {
      const YUVVideoDrawQuad* video_quad = YUVVideoDrawQuad::MaterialCast(quad);
      if (video_quad->protected_video_type != gfx::ProtectedVideoType::kClear) {
        force_quad_to_overlay = true;
        protected_video_type = video_quad->protected_video_type;
      }
    }

    if (quad->material == ContentDrawQuadBase::Material::kTextureContent) {
      const TextureDrawQuad* texture_quad = TextureDrawQuad::MaterialCast(quad);

      // Put hardware protected video into an overlay
      if (texture_quad->is_video_frame && texture_quad->protected_video_type !=
                                              gfx::ProtectedVideoType::kClear) {
        force_quad_to_overlay = true;
        protected_video_type = texture_quad->protected_video_type;
      }

      // Put HDR videos into an underlay.
      if (enable_hdr_underlays_) {
        if (resource_provider->GetColorSpace(texture_quad->resource_id())
                .IsHDR()) {
          force_quad_to_overlay = true;
        }
      }
    }

    if (force_quad_to_overlay) {
      if (!PutQuadInSeparateOverlay(it, resource_provider, render_pass,
                                    display_rect, quad, render_pass_filters,
                                    render_pass_backdrop_filters,
                                    protected_video_type, ca_layer_overlays)) {
        failed = true;
        break;
      }
    }
  }
  if (failed)
    ca_layer_overlays->clear();
}

bool CALayerOverlayProcessor::ProcessForCALayerOverlays(
    AggregatedRenderPass* render_pass,
    const DisplayResourceProvider* resource_provider,
    const gfx::RectF& display_rect,
    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
        render_pass_filters,
    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
        render_pass_backdrop_filters,
    OverlayCandidateList* ca_layer_overlays) {
  const QuadList& quad_list = render_pass->quad_list;
  gfx::CALayerResult result = gfx::kCALayerSuccess;
  size_t num_visible_quads = quad_list.size();

  // Skip overlay processing
  if (!overlays_allowed_ || !enable_ca_renderer_) {
    result = gfx::kCALayerFailedOverlayDisabled;
  } else if (render_pass->video_capture_enabled) {
    // The CARenderer is disabled when video capture is enabled.
    // https://crbug.com/836351, https://crbug.com/1290384
    result = gfx::kCALayerFailedVideoCaptureEnabled;
  } else if (!render_pass->copy_requests.empty()) {
    result = gfx::kCALayerFailedCopyRequests;
  }

  if (result != gfx::kCALayerSuccess) {
    RecordCALayerHistogram(result);
    SaveCALayerResult(result);
    return false;
  }

  // Start overlay processing
  ca_layer_overlays->reserve(num_visible_quads);

  int render_pass_draw_quad_count = 0;
  int yuv_draw_quad_count = 0;
  CALayerOverlayProcessorInternal processor;
  for (auto it = quad_list.BackToFrontBegin();
       result == gfx::kCALayerSuccess && it != quad_list.BackToFrontEnd();
       ++it) {
    const DrawQuad* quad = *it;
    OverlayCandidate ca_layer;
    bool skip = false;
    bool render_pass_draw_quad = false;
    result = processor.FromDrawQuad(
        resource_provider, display_rect, quad, render_pass_filters,
        render_pass_backdrop_filters, &ca_layer, &skip, &render_pass_draw_quad,
        yuv_draw_quad_count);
    if (result != gfx::kCALayerSuccess)
      break;

    if (render_pass_draw_quad) {
      ++render_pass_draw_quad_count;
      if (render_pass_draw_quad_count > kTooManyRenderPassDrawQuads) {
        result = gfx::kCALayerFailedTooManyRenderPassDrawQuads;
        break;
      }
    }

    if (skip)
      continue;

    if (!AreClipSettingsValid(ca_layer, ca_layer_overlays)) {
      result = gfx::kCALayerFailedDifferentClipSettings;
      break;
    }

    ca_layer_overlays->push_back(ca_layer);
  }

  // Fails if there are more draw quads than allowed for CoreAnimation.
  size_t max_number = (yuv_draw_quad_count < kMaxNumVideos)
                          ? layer_limit_default_
                          : layer_limit_with_many_videos_;
  if (num_visible_quads > max_number) {
    result = gfx::kCALayerFailedTooManyQuads;
  }

  RecordCALayerHistogram(result);
  SaveCALayerResult(result);

  if (result != gfx::kCALayerSuccess) {
    ca_layer_overlays->clear();
    return false;
  }
  return true;
}

bool CALayerOverlayProcessor::PutQuadInSeparateOverlay(
    QuadList::Iterator at,
    const DisplayResourceProvider* resource_provider,
    AggregatedRenderPass* render_pass,
    const gfx::RectF& display_rect,
    const DrawQuad* quad,
    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
        render_pass_filters,
    const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>&
        render_pass_backdrop_filters,
    gfx::ProtectedVideoType protected_video_type,
    OverlayCandidateList* ca_layer_overlays) const {
  CALayerOverlayProcessorInternal processor;
  OverlayCandidate ca_layer;
  bool skip = false;
  bool render_pass_draw_quad = false;
  int yuv_draw_quad_count = 0;
  gfx::CALayerResult result = processor.FromDrawQuad(
      resource_provider, display_rect, quad, render_pass_filters,
      render_pass_backdrop_filters, &ca_layer, &skip, &render_pass_draw_quad,
      yuv_draw_quad_count);
  if (result != gfx::kCALayerSuccess)
    return false;

  if (skip)
    return true;

  if (!AreClipSettingsValid(ca_layer, ca_layer_overlays))
    return true;

  ca_layer.protected_video_type = protected_video_type;
  render_pass->ReplaceExistingQuadWithSolidColor(at, SkColors::kTransparent,
                                                 SkBlendMode::kSrcOver);
  ca_layer_overlays->push_back(ca_layer);
  return true;
}

// Expand this function to save the results of the last N frames.
void CALayerOverlayProcessor::SaveCALayerResult(gfx::CALayerResult result) {
  ca_layer_result_ = result;
}

}  // namespace viz