| // Copyright 2015 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/output/ca_layer_overlay.h" |
| |
| #include <algorithm> |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "cc/quads/render_pass_draw_quad.h" |
| #include "cc/quads/solid_color_draw_quad.h" |
| #include "cc/quads/stream_video_draw_quad.h" |
| #include "cc/quads/texture_draw_quad.h" |
| #include "cc/quads/tile_draw_quad.h" |
| #include "cc/resources/resource_provider.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| |
| namespace cc { |
| |
| namespace { |
| |
| // 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; |
| |
| // This enum is used for histogram states and should only have new values added |
| // to the end before COUNT. |
| enum CALayerResult { |
| CA_LAYER_SUCCESS = 0, |
| CA_LAYER_FAILED_UNKNOWN, |
| CA_LAYER_FAILED_IO_SURFACE_NOT_CANDIDATE, |
| CA_LAYER_FAILED_STREAM_VIDEO_NOT_CANDIDATE, |
| CA_LAYER_FAILED_STREAM_VIDEO_TRANSFORM, |
| CA_LAYER_FAILED_TEXTURE_NOT_CANDIDATE, |
| CA_LAYER_FAILED_TEXTURE_Y_FLIPPED, |
| CA_LAYER_FAILED_TILE_NOT_CANDIDATE, |
| CA_LAYER_FAILED_QUAD_BLEND_MODE, |
| CA_LAYER_FAILED_QUAD_TRANSFORM, |
| CA_LAYER_FAILED_QUAD_CLIPPING, |
| CA_LAYER_FAILED_DEBUG_BORDER, |
| CA_LAYER_FAILED_PICTURE_CONTENT, |
| CA_LAYER_FAILED_RENDER_PASS, |
| CA_LAYER_FAILED_SURFACE_CONTENT, |
| CA_LAYER_FAILED_YUV_VIDEO_CONTENT, |
| CA_LAYER_FAILED_DIFFERENT_CLIP_SETTINGS, |
| CA_LAYER_FAILED_DIFFERENT_VERTEX_OPACITIES, |
| CA_LAYER_FAILED_RENDER_PASS_FILTER_SCALE, |
| CA_LAYER_FAILED_RENDER_PASS_BACKGROUND_FILTERS, |
| CA_LAYER_FAILED_RENDER_PASS_MASK, |
| CA_LAYER_FAILED_RENDER_PASS_FILTER_OPERATION, |
| CA_LAYER_FAILED_RENDER_PASS_SORTING_CONTEXT_ID, |
| CA_LAYER_FAILED_TOO_MANY_RENDER_PASS_DRAW_QUADS, |
| CA_LAYER_FAILED_COUNT, |
| }; |
| |
| bool FilterOperationSupported(const FilterOperation& operation) { |
| switch (operation.type()) { |
| case FilterOperation::GRAYSCALE: |
| case FilterOperation::SEPIA: |
| case FilterOperation::SATURATE: |
| case FilterOperation::HUE_ROTATE: |
| case FilterOperation::INVERT: |
| case FilterOperation::BRIGHTNESS: |
| case FilterOperation::CONTRAST: |
| case FilterOperation::OPACITY: |
| case FilterOperation::BLUR: |
| case FilterOperation::DROP_SHADOW: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static const FilterOperations* FiltersForPass( |
| int render_pass_id, |
| const RenderPassFilterList& filter_list) { |
| auto it = std::lower_bound( |
| filter_list.begin(), filter_list.end(), |
| std::pair<int, FilterOperations*>(render_pass_id, nullptr)); |
| if (it != filter_list.end() && it->first == render_pass_id) |
| return it->second; |
| return nullptr; |
| } |
| |
| CALayerResult FromRenderPassQuad( |
| ResourceProvider* resource_provider, |
| const RenderPassDrawQuad* quad, |
| const RenderPassFilterList& render_pass_filters, |
| const RenderPassFilterList& render_pass_background_filters, |
| CALayerOverlay* ca_layer_overlay) { |
| if (FiltersForPass(quad->render_pass_id, render_pass_background_filters)) { |
| return CA_LAYER_FAILED_RENDER_PASS_BACKGROUND_FILTERS; |
| } |
| |
| if (quad->shared_quad_state->sorting_context_id != 0) |
| return CA_LAYER_FAILED_RENDER_PASS_SORTING_CONTEXT_ID; |
| |
| const FilterOperations* filters = |
| FiltersForPass(quad->render_pass_id, render_pass_filters); |
| if (filters) { |
| for (const FilterOperation& operation : filters->operations()) { |
| bool success = FilterOperationSupported(operation); |
| if (!success) |
| return CA_LAYER_FAILED_RENDER_PASS_FILTER_OPERATION; |
| } |
| } |
| |
| ca_layer_overlay->rpdq = quad; |
| ca_layer_overlay->contents_rect = gfx::RectF(0, 0, 1, 1); |
| |
| return CA_LAYER_SUCCESS; |
| } |
| |
| CALayerResult FromStreamVideoQuad(ResourceProvider* resource_provider, |
| const StreamVideoDrawQuad* quad, |
| CALayerOverlay* ca_layer_overlay) { |
| unsigned resource_id = quad->resource_id(); |
| if (!resource_provider->IsOverlayCandidate(resource_id)) |
| return CA_LAYER_FAILED_STREAM_VIDEO_NOT_CANDIDATE; |
| ca_layer_overlay->contents_resource_id = resource_id; |
| // TODO(ccameron): Support merging at least some basic transforms into the |
| // layer transform. |
| if (!quad->matrix.IsIdentity()) |
| return CA_LAYER_FAILED_STREAM_VIDEO_TRANSFORM; |
| ca_layer_overlay->contents_rect = gfx::RectF(0, 0, 1, 1); |
| return CA_LAYER_SUCCESS; |
| } |
| |
| CALayerResult FromSolidColorDrawQuad(const SolidColorDrawQuad* quad, |
| CALayerOverlay* ca_layer_overlay, |
| bool* skip) { |
| // Do not generate quads that are completely transparent. |
| if (SkColorGetA(quad->color) == 0) { |
| *skip = true; |
| return CA_LAYER_SUCCESS; |
| } |
| ca_layer_overlay->background_color = quad->color; |
| return CA_LAYER_SUCCESS; |
| } |
| |
| CALayerResult FromTextureQuad(ResourceProvider* resource_provider, |
| const TextureDrawQuad* quad, |
| CALayerOverlay* ca_layer_overlay) { |
| unsigned resource_id = quad->resource_id(); |
| if (!resource_provider->IsOverlayCandidate(resource_id)) |
| return CA_LAYER_FAILED_TEXTURE_NOT_CANDIDATE; |
| if (quad->y_flipped) { |
| // 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. |
| ca_layer_overlay->shared_state->transform.preTranslate( |
| 0, ca_layer_overlay->bounds_rect.height(), 0); |
| ca_layer_overlay->shared_state->transform.preScale(1, -1, 1); |
| } |
| ca_layer_overlay->contents_resource_id = resource_id; |
| ca_layer_overlay->contents_rect = |
| BoundingRect(quad->uv_top_left, quad->uv_bottom_right); |
| ca_layer_overlay->background_color = quad->background_color; |
| for (int i = 1; i < 4; ++i) { |
| if (quad->vertex_opacity[i] != quad->vertex_opacity[0]) |
| return CA_LAYER_FAILED_DIFFERENT_VERTEX_OPACITIES; |
| } |
| ca_layer_overlay->shared_state->opacity *= quad->vertex_opacity[0]; |
| ca_layer_overlay->filter = quad->nearest_neighbor ? GL_NEAREST : GL_LINEAR; |
| return CA_LAYER_SUCCESS; |
| } |
| |
| CALayerResult FromTileQuad(ResourceProvider* resource_provider, |
| const TileDrawQuad* quad, |
| CALayerOverlay* ca_layer_overlay) { |
| unsigned resource_id = quad->resource_id(); |
| if (!resource_provider->IsOverlayCandidate(resource_id)) |
| return CA_LAYER_FAILED_TILE_NOT_CANDIDATE; |
| ca_layer_overlay->contents_resource_id = resource_id; |
| ca_layer_overlay->contents_rect = quad->tex_coord_rect; |
| ca_layer_overlay->contents_rect.Scale(1.f / quad->texture_size.width(), |
| 1.f / quad->texture_size.height()); |
| return CA_LAYER_SUCCESS; |
| } |
| |
| class CALayerOverlayProcessor { |
| public: |
| CALayerResult FromDrawQuad( |
| ResourceProvider* resource_provider, |
| const gfx::RectF& display_rect, |
| const DrawQuad* quad, |
| const RenderPassFilterList& render_pass_filters, |
| const RenderPassFilterList& render_pass_background_filters, |
| CALayerOverlay* ca_layer_overlay, |
| bool* skip, |
| bool* render_pass_draw_quad) { |
| if (quad->shared_quad_state->blend_mode != SkBlendMode::kSrcOver) |
| return CA_LAYER_FAILED_QUAD_BLEND_MODE; |
| |
| // Early-out for invisible quads. |
| if (quad->shared_quad_state->opacity == 0.f) { |
| *skip = true; |
| return CA_LAYER_SUCCESS; |
| } |
| |
| // Enable edge anti-aliasing only on layer boundaries. |
| ca_layer_overlay->edge_aa_mask = 0; |
| if (quad->IsLeftEdge()) |
| ca_layer_overlay->edge_aa_mask |= GL_CA_LAYER_EDGE_LEFT_CHROMIUM; |
| if (quad->IsRightEdge()) |
| ca_layer_overlay->edge_aa_mask |= GL_CA_LAYER_EDGE_RIGHT_CHROMIUM; |
| if (quad->IsBottomEdge()) |
| ca_layer_overlay->edge_aa_mask |= GL_CA_LAYER_EDGE_BOTTOM_CHROMIUM; |
| if (quad->IsTopEdge()) |
| ca_layer_overlay->edge_aa_mask |= GL_CA_LAYER_EDGE_TOP_CHROMIUM; |
| |
| if (most_recent_shared_quad_state_ != quad->shared_quad_state) { |
| most_recent_shared_quad_state_ = quad->shared_quad_state; |
| most_recent_overlay_shared_state_ = new CALayerOverlaySharedState; |
| // Set rect clipping and sorting context ID. |
| most_recent_overlay_shared_state_->sorting_context_id = |
| quad->shared_quad_state->sorting_context_id; |
| most_recent_overlay_shared_state_->is_clipped = |
| quad->shared_quad_state->is_clipped; |
| most_recent_overlay_shared_state_->clip_rect = |
| gfx::RectF(quad->shared_quad_state->clip_rect); |
| |
| most_recent_overlay_shared_state_->opacity = |
| quad->shared_quad_state->opacity; |
| most_recent_overlay_shared_state_->transform = |
| quad->shared_quad_state->quad_to_target_transform.matrix(); |
| } |
| ca_layer_overlay->shared_state = most_recent_overlay_shared_state_; |
| |
| ca_layer_overlay->bounds_rect = gfx::RectF(quad->rect); |
| |
| *render_pass_draw_quad = quad->material == DrawQuad::RENDER_PASS; |
| switch (quad->material) { |
| case DrawQuad::TEXTURE_CONTENT: |
| return FromTextureQuad(resource_provider, |
| TextureDrawQuad::MaterialCast(quad), |
| ca_layer_overlay); |
| case DrawQuad::TILED_CONTENT: |
| return FromTileQuad(resource_provider, TileDrawQuad::MaterialCast(quad), |
| ca_layer_overlay); |
| case DrawQuad::SOLID_COLOR: |
| return FromSolidColorDrawQuad(SolidColorDrawQuad::MaterialCast(quad), |
| ca_layer_overlay, skip); |
| case DrawQuad::STREAM_VIDEO_CONTENT: |
| return FromStreamVideoQuad(resource_provider, |
| StreamVideoDrawQuad::MaterialCast(quad), |
| ca_layer_overlay); |
| case DrawQuad::DEBUG_BORDER: |
| return CA_LAYER_FAILED_DEBUG_BORDER; |
| case DrawQuad::PICTURE_CONTENT: |
| return CA_LAYER_FAILED_PICTURE_CONTENT; |
| case DrawQuad::RENDER_PASS: |
| return FromRenderPassQuad( |
| resource_provider, RenderPassDrawQuad::MaterialCast(quad), |
| render_pass_filters, render_pass_background_filters, |
| ca_layer_overlay); |
| case DrawQuad::SURFACE_CONTENT: |
| return CA_LAYER_FAILED_SURFACE_CONTENT; |
| case DrawQuad::YUV_VIDEO_CONTENT: |
| return CA_LAYER_FAILED_YUV_VIDEO_CONTENT; |
| default: |
| break; |
| } |
| |
| return CA_LAYER_FAILED_UNKNOWN; |
| } |
| |
| private: |
| const SharedQuadState* most_recent_shared_quad_state_ = nullptr; |
| scoped_refptr<CALayerOverlaySharedState> most_recent_overlay_shared_state_; |
| }; |
| |
| } // namespace |
| |
| CALayerOverlay::CALayerOverlay() : filter(GL_LINEAR) {} |
| |
| CALayerOverlay::CALayerOverlay(const CALayerOverlay& other) = default; |
| |
| CALayerOverlay::~CALayerOverlay() {} |
| |
| bool ProcessForCALayerOverlays( |
| ResourceProvider* resource_provider, |
| const gfx::RectF& display_rect, |
| const QuadList& quad_list, |
| const RenderPassFilterList& render_pass_filters, |
| const RenderPassFilterList& render_pass_background_filters, |
| CALayerOverlayList* ca_layer_overlays) { |
| CALayerResult result = CA_LAYER_SUCCESS; |
| ca_layer_overlays->reserve(quad_list.size()); |
| |
| int render_pass_draw_quad_count = 0; |
| CALayerOverlayProcessor processor; |
| for (auto it = quad_list.BackToFrontBegin(); it != quad_list.BackToFrontEnd(); |
| ++it) { |
| const DrawQuad* quad = *it; |
| CALayerOverlay ca_layer; |
| bool skip = false; |
| bool render_pass_draw_quad = false; |
| result = processor.FromDrawQuad(resource_provider, display_rect, quad, |
| render_pass_filters, |
| render_pass_background_filters, &ca_layer, |
| &skip, &render_pass_draw_quad); |
| if (result != CA_LAYER_SUCCESS) |
| break; |
| |
| if (render_pass_draw_quad) { |
| ++render_pass_draw_quad_count; |
| if (render_pass_draw_quad_count > kTooManyRenderPassDrawQuads) { |
| result = CA_LAYER_FAILED_TOO_MANY_RENDER_PASS_DRAW_QUADS; |
| break; |
| } |
| } |
| |
| if (skip) |
| continue; |
| |
| // It is not possible to correctly represent two different clipping settings |
| // within one sorting context. |
| if (!ca_layer_overlays->empty()) { |
| const CALayerOverlay& previous_ca_layer = ca_layer_overlays->back(); |
| if (ca_layer.shared_state->sorting_context_id && |
| previous_ca_layer.shared_state->sorting_context_id == |
| ca_layer.shared_state->sorting_context_id) { |
| if (previous_ca_layer.shared_state->is_clipped != |
| ca_layer.shared_state->is_clipped || |
| previous_ca_layer.shared_state->clip_rect != |
| ca_layer.shared_state->clip_rect) { |
| result = CA_LAYER_FAILED_DIFFERENT_CLIP_SETTINGS; |
| break; |
| } |
| } |
| } |
| |
| ca_layer_overlays->push_back(ca_layer); |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("Compositing.Renderer.CALayerResult", result, |
| CA_LAYER_FAILED_COUNT); |
| |
| if (result != CA_LAYER_SUCCESS) { |
| ca_layer_overlays->clear(); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace cc |