| // Copyright 2012 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 "components/viz/service/display/skia_renderer.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/bind.h" |
| #include "base/bits.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/optional.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "cc/base/math_util.h" |
| #include "cc/debug/debug_colors.h" |
| #include "cc/paint/render_surface_filters.h" |
| #include "components/viz/common/display/renderer_settings.h" |
| #include "components/viz/common/frame_sinks/copy_output_request.h" |
| #include "components/viz/common/frame_sinks/copy_output_util.h" |
| #include "components/viz/common/quads/aggregated_render_pass_draw_quad.h" |
| #include "components/viz/common/quads/compositor_render_pass_draw_quad.h" |
| #include "components/viz/common/quads/debug_border_draw_quad.h" |
| #include "components/viz/common/quads/picture_draw_quad.h" |
| #include "components/viz/common/quads/solid_color_draw_quad.h" |
| #include "components/viz/common/quads/stream_video_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/common/resources/platform_color.h" |
| #include "components/viz/common/resources/resource_format_utils.h" |
| #include "components/viz/common/skia_helper.h" |
| #include "components/viz/service/display/delegated_ink_point_renderer_skia.h" |
| #include "components/viz/service/display/display_resource_provider.h" |
| #include "components/viz/service/display/output_surface.h" |
| #include "components/viz/service/display/output_surface_frame.h" |
| #include "components/viz/service/display/renderer_utils.h" |
| #include "components/viz/service/display/resource_fence.h" |
| #include "components/viz/service/display/skia_output_surface.h" |
| #include "gpu/command_buffer/client/gles2_interface.h" |
| #include "gpu/command_buffer/common/sync_token.h" |
| #include "skia/ext/opacity_filter_canvas.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "third_party/skia/include/core/SkColorFilter.h" |
| #include "third_party/skia/include/core/SkDeferredDisplayList.h" |
| #include "third_party/skia/include/core/SkMatrix.h" |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "third_party/skia/include/core/SkPixelRef.h" |
| #include "third_party/skia/include/core/SkShader.h" |
| #include "third_party/skia/include/core/SkString.h" |
| #include "third_party/skia/include/effects/SkColorFilterImageFilter.h" |
| #include "third_party/skia/include/effects/SkColorMatrix.h" |
| #include "third_party/skia/include/effects/SkGradientShader.h" |
| #include "third_party/skia/include/effects/SkImageFilters.h" |
| #include "third_party/skia/include/effects/SkOverdrawColorFilter.h" |
| #include "third_party/skia/include/effects/SkRuntimeEffect.h" |
| #include "third_party/skia/include/effects/SkShaderMaskFilter.h" |
| #include "third_party/skia/include/gpu/GrBackendSurface.h" |
| #include "third_party/skia/include/gpu/GrDirectContext.h" |
| #include "third_party/skia/include/third_party/skcms/skcms.h" |
| #include "ui/gfx/color_transform.h" |
| #include "ui/gfx/geometry/axis_transform2d.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/skia_util.h" |
| #include "ui/gfx/transform.h" |
| #include "ui/gfx/transform_util.h" |
| |
| #if defined(USE_OZONE) |
| #include "ui/base/ui_base_features.h" |
| #endif |
| |
| namespace viz { |
| |
| namespace { |
| |
| // Smallest unit that impacts anti-aliasing output. We use this to determine |
| // when an exterior edge (with AA) has been clipped (no AA). The specific value |
| // was chosen to match that used by gl_renderer. |
| static const float kAAEpsilon = 1.0f / 1024.0f; |
| |
| // The gfx::QuadF draw_region passed to DoDrawQuad, converted to Skia types |
| struct SkDrawRegion { |
| SkDrawRegion() = default; |
| explicit SkDrawRegion(const gfx::QuadF& draw_region); |
| |
| SkPoint points[4]; |
| }; |
| |
| // Additional YUV information to skia renderer to draw 9- and 10- bits color. |
| struct YUVInput { |
| YUVInput() { memset(this, 0, sizeof(*this)); } |
| float offset; |
| float multiplier; |
| }; |
| |
| SkDrawRegion::SkDrawRegion(const gfx::QuadF& draw_region) { |
| points[0] = gfx::PointFToSkPoint(draw_region.p1()); |
| points[1] = gfx::PointFToSkPoint(draw_region.p2()); |
| points[2] = gfx::PointFToSkPoint(draw_region.p3()); |
| points[3] = gfx::PointFToSkPoint(draw_region.p4()); |
| } |
| |
| bool IsTextureResource(DisplayResourceProvider* resource_provider, |
| ResourceId resource_id) { |
| return !resource_provider->IsResourceSoftwareBacked(resource_id); |
| } |
| |
| void ApplyExplicitScissor(const DrawQuad* quad, |
| const gfx::Rect& scissor_rect, |
| const gfx::Transform& device_transform, |
| unsigned* aa_flags, |
| gfx::RectF* vis_rect) { |
| // Inset rectangular edges and turn off the AA for clipped edges. Operates in |
| // the quad's space, so apply inverse of transform to get new scissor |
| gfx::RectF scissor(scissor_rect); |
| device_transform.TransformRectReverse(&scissor); |
| |
| float left_inset = scissor.x() - vis_rect->x(); |
| float top_inset = scissor.y() - vis_rect->y(); |
| float right_inset = vis_rect->right() - scissor.right(); |
| float bottom_inset = vis_rect->bottom() - scissor.bottom(); |
| |
| if (left_inset >= kAAEpsilon) { |
| *aa_flags &= ~SkCanvas::kLeft_QuadAAFlag; |
| } else { |
| left_inset = 0; |
| } |
| if (top_inset >= kAAEpsilon) { |
| *aa_flags &= ~SkCanvas::kTop_QuadAAFlag; |
| } else { |
| top_inset = 0; |
| } |
| if (right_inset >= kAAEpsilon) { |
| *aa_flags &= ~SkCanvas::kRight_QuadAAFlag; |
| } else { |
| right_inset = 0; |
| } |
| if (bottom_inset >= kAAEpsilon) { |
| *aa_flags &= ~SkCanvas::kBottom_QuadAAFlag; |
| } else { |
| bottom_inset = 0; |
| } |
| |
| vis_rect->Inset(left_inset, top_inset, right_inset, bottom_inset); |
| } |
| |
| unsigned GetCornerAAFlags(const DrawQuad* quad, |
| const SkPoint& vertex, |
| unsigned edge_mask) { |
| // Returns mask of SkCanvas::QuadAAFlags, with bits set for each edge of the |
| // shared quad state's quad_layer_rect that vertex is touching. |
| |
| unsigned mask = SkCanvas::kNone_QuadAAFlags; |
| if (std::abs(vertex.x()) < kAAEpsilon) |
| mask |= SkCanvas::kLeft_QuadAAFlag; |
| if (std::abs(vertex.x() - quad->shared_quad_state->quad_layer_rect.width()) < |
| kAAEpsilon) |
| mask |= SkCanvas::kRight_QuadAAFlag; |
| if (std::abs(vertex.y()) < kAAEpsilon) |
| mask |= SkCanvas::kTop_QuadAAFlag; |
| if (std::abs(vertex.y() - quad->shared_quad_state->quad_layer_rect.height()) < |
| kAAEpsilon) |
| mask |= SkCanvas::kBottom_QuadAAFlag; |
| // & with the overall edge_mask to take into account edges that were clipped |
| // by the visible rect. |
| return mask & edge_mask; |
| } |
| |
| bool IsExteriorEdge(unsigned corner_mask1, unsigned corner_mask2) { |
| return (corner_mask1 & corner_mask2) != 0; |
| } |
| |
| unsigned GetRectilinearEdgeFlags(const DrawQuad* quad) { |
| // In the normal case, turn on AA for edges that represent the outside of |
| // the layer, and that aren't clipped by the visible rect. |
| unsigned mask = SkCanvas::kNone_QuadAAFlags; |
| if (quad->IsLeftEdge() && |
| std::abs(quad->rect.x() - quad->visible_rect.x()) < kAAEpsilon) |
| mask |= SkCanvas::kLeft_QuadAAFlag; |
| if (quad->IsTopEdge() && |
| std::abs(quad->rect.y() - quad->visible_rect.y()) < kAAEpsilon) |
| mask |= SkCanvas::kTop_QuadAAFlag; |
| if (quad->IsRightEdge() && |
| std::abs(quad->rect.right() - quad->visible_rect.right()) < kAAEpsilon) |
| mask |= SkCanvas::kRight_QuadAAFlag; |
| if (quad->IsBottomEdge() && |
| std::abs(quad->rect.bottom() - quad->visible_rect.bottom()) < kAAEpsilon) |
| mask |= SkCanvas::kBottom_QuadAAFlag; |
| |
| return mask; |
| } |
| |
| // This also modifies draw_region to clean up any degeneracies |
| void GetClippedEdgeFlags(const DrawQuad* quad, |
| unsigned* edge_mask, |
| SkDrawRegion* draw_region) { |
| // Instead of trying to rotate vertices of draw_region to align with Skia's |
| // edge label conventions, turn on an edge's label if it is aligned to any |
| // exterior edge. |
| unsigned p0Mask = GetCornerAAFlags(quad, draw_region->points[0], *edge_mask); |
| unsigned p1Mask = GetCornerAAFlags(quad, draw_region->points[1], *edge_mask); |
| unsigned p2Mask = GetCornerAAFlags(quad, draw_region->points[2], *edge_mask); |
| unsigned p3Mask = GetCornerAAFlags(quad, draw_region->points[3], *edge_mask); |
| |
| unsigned mask = SkCanvas::kNone_QuadAAFlags; |
| // The "top" is p0 to p1 |
| if (IsExteriorEdge(p0Mask, p1Mask)) |
| mask |= SkCanvas::kTop_QuadAAFlag; |
| // The "right" is p1 to p2 |
| if (IsExteriorEdge(p1Mask, p2Mask)) |
| mask |= SkCanvas::kRight_QuadAAFlag; |
| // The "bottom" is p2 to p3 |
| if (IsExteriorEdge(p2Mask, p3Mask)) |
| mask |= SkCanvas::kBottom_QuadAAFlag; |
| // The "left" is p3 to p0 |
| if (IsExteriorEdge(p3Mask, p0Mask)) |
| mask |= SkCanvas::kLeft_QuadAAFlag; |
| |
| // If the clipped draw_region has adjacent non-AA edges that touch the |
| // exterior edge (which should be AA'ed), move the degenerate vertex to the |
| // appropriate index so that Skia knows to construct a coverage ramp at that |
| // corner. This is not an ideal solution, but is the best hint we can give, |
| // given our limited information post-BSP splitting. |
| if (draw_region->points[2] == draw_region->points[3]) { |
| // The BSP splitting always creates degenerate quads with the duplicate |
| // vertex in the last two indices. |
| if (p0Mask && !(mask & SkCanvas::kLeft_QuadAAFlag) && |
| !(mask & SkCanvas::kTop_QuadAAFlag)) { |
| // Rewrite draw_region from p0,p1,p2,p2 to p0,p1,p2,p0; top edge stays off |
| // right edge is preserved, bottom edge turns off, left edge turns on |
| draw_region->points[3] = draw_region->points[0]; |
| mask = SkCanvas::kLeft_QuadAAFlag | (mask & SkCanvas::kRight_QuadAAFlag); |
| } else if (p1Mask && !(mask & SkCanvas::kTop_QuadAAFlag) && |
| !(mask & SkCanvas::kRight_QuadAAFlag)) { |
| // Rewrite draw_region to p0,p1,p1,p2; top edge stays off, right edge |
| // turns on, bottom edge turns off, left edge is preserved |
| draw_region->points[2] = draw_region->points[1]; |
| mask = SkCanvas::kRight_QuadAAFlag | (mask & SkCanvas::kLeft_QuadAAFlag); |
| } |
| // p2 could follow the same process, but if its adjacent edges are AA |
| // (skipping the degenerate edge to p3), it's actually already in the |
| // desired vertex ordering; and since p3 is in the same location, it's |
| // equivalent to p2 so it doesn't need checking either. |
| } // Else not degenerate, so can't to correct non-AA corners touching AA edge |
| |
| *edge_mask = mask; |
| } |
| |
| bool IsAAForcedOff(const DrawQuad* quad) { |
| switch (quad->material) { |
| case DrawQuad::Material::kPictureContent: |
| return PictureDrawQuad::MaterialCast(quad)->force_anti_aliasing_off; |
| case DrawQuad::Material::kCompositorRenderPass: |
| // We should not have compositor render passes here. |
| NOTREACHED(); |
| return CompositorRenderPassDrawQuad::MaterialCast(quad) |
| ->force_anti_aliasing_off; |
| case DrawQuad::Material::kAggregatedRenderPass: |
| return AggregatedRenderPassDrawQuad::MaterialCast(quad) |
| ->force_anti_aliasing_off; |
| case DrawQuad::Material::kSolidColor: |
| return SolidColorDrawQuad::MaterialCast(quad)->force_anti_aliasing_off; |
| case DrawQuad::Material::kTiledContent: |
| return TileDrawQuad::MaterialCast(quad)->force_anti_aliasing_off; |
| case DrawQuad::Material::kYuvVideoContent: |
| case DrawQuad::Material::kStreamVideoContent: |
| case DrawQuad::Material::kTextureContent: |
| // This is done to match the behaviour of GLRenderer and we can revisit it |
| // later. |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool UseNearestNeighborSampling(const DrawQuad* quad) { |
| switch (quad->material) { |
| case DrawQuad::Material::kPictureContent: |
| return PictureDrawQuad::MaterialCast(quad)->nearest_neighbor; |
| case DrawQuad::Material::kTextureContent: |
| return TextureDrawQuad::MaterialCast(quad)->nearest_neighbor; |
| case DrawQuad::Material::kTiledContent: |
| return TileDrawQuad::MaterialCast(quad)->nearest_neighbor; |
| default: |
| // Other quad types do not expose nearest_neighbor. |
| return false; |
| } |
| } |
| |
| SkFilterQuality GetFilterQuality(const DrawQuad* quad) { |
| if (UseNearestNeighborSampling(quad)) |
| return kNone_SkFilterQuality; |
| |
| // Default to bilinear if the quad doesn't specify nearest_neighbor. |
| // TODO(penghuang): figure out how to set correct filter quality for YUV and |
| // video stream quads. |
| return kLow_SkFilterQuality; |
| } |
| |
| // Returns kFast if sampling outside of vis_tex_coords due to AA or bilerp will |
| // not go outside of the content area, or if the content area is the full image |
| // (in which case hardware clamping handles it automatically). Different quad |
| // types have different rules for the content area within the image. |
| SkCanvas::SrcRectConstraint GetTextureConstraint( |
| const SkImage* image, |
| const gfx::RectF& vis_tex_coords, |
| const gfx::RectF& valid_texel_bounds) { |
| bool fills_left = valid_texel_bounds.x() <= 0.f; |
| bool fills_right = valid_texel_bounds.right() >= image->width(); |
| bool fills_top = valid_texel_bounds.y() <= 0.f; |
| bool fills_bottom = valid_texel_bounds.bottom() >= image->height(); |
| if (fills_left && fills_right && fills_top && fills_bottom) { |
| // The entire image is contained in the content area, so hardware clamping |
| // ensures only content texels are sampled |
| return SkCanvas::kFast_SrcRectConstraint; |
| } |
| |
| gfx::RectF safe_texels = valid_texel_bounds; |
| safe_texels.Inset(0.5f, 0.5f); |
| |
| // Check each axis independently; tile quads may only need clamping on one |
| // side (e.g. right or bottom) and this logic doesn't fully match a simple |
| // contains() check. |
| if ((!fills_left && vis_tex_coords.x() < safe_texels.x()) || |
| (!fills_right && vis_tex_coords.right() > safe_texels.right())) { |
| return SkCanvas::kStrict_SrcRectConstraint; |
| } |
| if ((!fills_top && vis_tex_coords.y() < safe_texels.y()) || |
| (!fills_bottom && vis_tex_coords.bottom() > safe_texels.bottom())) { |
| return SkCanvas::kStrict_SrcRectConstraint; |
| } |
| |
| // The texture coordinates are far enough from the content area that even with |
| // bilerp and AA, it won't sample outside the content area |
| return SkCanvas::kFast_SrcRectConstraint; |
| } |
| |
| // Return a color filter that multiplies the incoming color by the fixed alpha |
| sk_sp<SkColorFilter> MakeOpacityFilter(float alpha, sk_sp<SkColorFilter> in) { |
| SkColor alpha_as_color = SkColorSetA(SK_ColorWHITE, 255 * alpha); |
| // MakeModeFilter treats fixed color as src, and input color as dst. |
| // kDstIn is (srcAlpha * dstColor, srcAlpha * dstAlpha) so this makes the |
| // output color equal to input color * alpha. |
| sk_sp<SkColorFilter> opacity = |
| SkColorFilters::Blend(alpha_as_color, SkBlendMode::kDstIn); |
| if (in) { |
| return opacity->makeComposed(std::move(in)); |
| } else { |
| return opacity; |
| } |
| } |
| |
| // Porter-Duff blend mode utility functions, where the final color is |
| // represented as a weighted sum of the incoming src and existing dst color. |
| // See [https://skia.org/user/api/SkBlendMode_Reference] |
| |
| bool IsPorterDuffBlendMode(SkBlendMode blendMode) { |
| return blendMode <= SkBlendMode::kLastCoeffMode; |
| } |
| |
| // Returns true if drawing transparent black with |blendMode| would modify the |
| // destination buffer. If false is returned, the draw would have no discernible |
| // effect on the pixel color so the entire draw can be skipped. |
| bool TransparentBlackAffectsOutput(SkBlendMode blendMode) { |
| SkBlendModeCoeff src, dst; |
| if (!SkBlendMode_AsCoeff(blendMode, &src, &dst)) { |
| // An advanced blend mode that can't be represented as coefficients, so |
| // assume it modifies the output. |
| return true; |
| } |
| // True when the dst coefficient is not equal to 1 (when src = (0,0,0,0)) |
| return dst != SkBlendModeCoeff::kOne && dst != SkBlendModeCoeff::kISA && |
| dst != SkBlendModeCoeff::kISC; |
| } |
| |
| // Returns true if src content drawn with |blendMode| into a RenderPass would |
| // produce the exact same image as the original src content. |
| bool RenderPassPreservesContent(SkBlendMode blendMode) { |
| SkBlendModeCoeff src, dst; |
| if (!SkBlendMode_AsCoeff(blendMode, &src, &dst)) { |
| return false; |
| } |
| // True when src coefficient is equal to 1 (when dst = (0,0,0,0)) |
| return src == SkBlendModeCoeff::kOne || src == SkBlendModeCoeff::kIDA || |
| src == SkBlendModeCoeff::kIDC; |
| } |
| |
| // Returns true if src content draw with |blendMode| into an empty RenderPass |
| // would produce a transparent black image. |
| bool RenderPassRemainsTransparent(SkBlendMode blendMode) { |
| SkBlendModeCoeff src, dst; |
| if (!SkBlendMode_AsCoeff(blendMode, &src, &dst)) { |
| return false; |
| } |
| |
| // True when src coefficient is equal to 0 (when dst = (0,0,0,0)) |
| return src == SkBlendModeCoeff::kZero || src == SkBlendModeCoeff::kDA || |
| src == SkBlendModeCoeff::kDC; |
| } |
| |
| } // namespace |
| |
| // chrome style prevents this from going in skia_renderer.h, but since it |
| // uses base::Optional, the style also requires it to have a declared ctor |
| SkiaRenderer::BatchedQuadState::BatchedQuadState() = default; |
| |
| // Parameters needed to draw a CompositorRenderPassDrawQuad. |
| struct SkiaRenderer::DrawRPDQParams { |
| explicit DrawRPDQParams(const gfx::RectF& visible_rect); |
| |
| // Root of the calculated image filter DAG to be applied to the render pass. |
| sk_sp<SkImageFilter> image_filter = nullptr; |
| // If |image_filter| can be represented as a single color filter, this will |
| // be that filter. |image_filter| will still be non-null. |
| sk_sp<SkColorFilter> color_filter = nullptr; |
| // Root of the calculated backdrop filter DAG to be applied to the render pass |
| sk_sp<SkImageFilter> backdrop_filter = nullptr; |
| // Resolved mask image and calculated transform matrix baked into an SkShader, |
| // which will be applied using SkCanvas::clipShader in RPDQ's coord space. |
| sk_sp<SkShader> mask_shader = nullptr; |
| // Backdrop border box for the render pass, to clip backdrop-filtered content |
| base::Optional<gfx::RRectF> backdrop_filter_bounds; |
| // The content space bounds that includes any filtered extents. If empty, |
| // the draw can be skipped. |
| gfx::Rect filter_bounds; |
| // The additional matrix to concatenate to the SkCanvas after image filters |
| // have been configured so that the DrawQuadParams geometry is properly mapped |
| // (i.e. when set, |visible_rect| and |draw_region| must be pre-transformed by |
| // this before |content_device_transform|). |
| base::Optional<SkMatrix> bypass_transform; |
| // The pre-filter clip to apply to the bypassed content of the RenderPass. |
| // This limits the bypassed content to the output rect of the RenderPass; it |
| // is in the same space as |backdrop_filter_bounds| and |filter_bounds|. |
| base::Optional<gfx::RectF> bypass_clip; |
| |
| // True when there is an |image_filter| and it's not equivalent to |
| // |color_filter|. |
| bool has_complex_image_filter() const { |
| return image_filter && !color_filter; |
| } |
| |
| // True if the RenderPass's output rect would clip the visible contents that |
| // are bypassing the renderpass' offscreen buffer. |
| bool needs_bypass_clip(const gfx::RectF& content_rect) const { |
| if (bypass_clip.has_value()) { |
| DCHECK(bypass_transform.has_value()); |
| SkRect content_bounds = |
| bypass_transform->mapRect(gfx::RectFToSkRect(content_rect)); |
| return !bypass_clip->Contains(gfx::SkRectToRectF(content_bounds)); |
| } else { |
| return false; |
| } |
| } |
| }; |
| |
| SkiaRenderer::DrawRPDQParams::DrawRPDQParams(const gfx::RectF& visible_rect) |
| : filter_bounds(gfx::ToEnclosingRect(visible_rect)) {} |
| |
| // State calculated from a DrawQuad and current renderer state, that is common |
| // to all DrawQuad rendering. |
| struct SkiaRenderer::DrawQuadParams { |
| DrawQuadParams() = default; |
| DrawQuadParams(const gfx::Transform& cdt, |
| const gfx::RectF& visible_rect, |
| unsigned aa_flags, |
| SkBlendMode blend_mode, |
| float opacity, |
| SkFilterQuality filter_quality, |
| const gfx::QuadF* draw_region); |
| |
| // window_matrix * projection_matrix * quad_to_target_transform normally, |
| // or quad_to_target_transform if the remaining device transform is held in |
| // the DrawRPDQParams for a bypass quad. |
| gfx::Transform content_device_transform; |
| // The DrawQuad's visible_rect, possibly explicitly clipped by the scissor |
| gfx::RectF visible_rect; |
| // Initialized to the visible_rect, relevant quad types should updated based |
| // on their specialized properties. |
| gfx::RectF vis_tex_coords; |
| // SkCanvas::QuadAAFlags, already taking into account settings |
| // (but not certain quad type's force_antialias_off bit) |
| unsigned aa_flags; |
| // Final blend mode to use, respecting quad settings + opacity optimizations |
| SkBlendMode blend_mode; |
| // Final opacity of quad |
| float opacity; |
| // Resolved filter quality from quad settings |
| SkFilterQuality filter_quality; |
| // Optional restricted draw geometry, will point to a length 4 SkPoint array |
| // with its points in CW order matching Skia's vertex/edge expectations. |
| base::Optional<SkDrawRegion> draw_region; |
| // Optional rounded corner clip to apply. If present, it will have been |
| // transformed to device space and ShouldApplyRoundedCorner returns true. |
| base::Optional<gfx::RRectF> rounded_corner_bounds; |
| // Optional device space clip to apply. If present, it is equal to the current |
| // |scissor_rect_| of the renderer. |
| base::Optional<gfx::Rect> scissor_rect; |
| |
| SkPaint paint(sk_sp<SkColorFilter> color_filter) const { |
| SkPaint p; |
| if (color_filter) { |
| p.setColorFilter(color_filter); |
| } |
| p.setFilterQuality(filter_quality); |
| p.setBlendMode(blend_mode); |
| p.setAlphaf(opacity); |
| p.setAntiAlias(aa_flags != SkCanvas::kNone_QuadAAFlags); |
| return p; |
| } |
| }; |
| |
| SkiaRenderer::DrawQuadParams::DrawQuadParams(const gfx::Transform& cdt, |
| const gfx::RectF& visible_rect, |
| unsigned aa_flags, |
| SkBlendMode blend_mode, |
| float opacity, |
| SkFilterQuality filter_quality, |
| const gfx::QuadF* draw_region) |
| : content_device_transform(cdt), |
| visible_rect(visible_rect), |
| vis_tex_coords(visible_rect), |
| aa_flags(aa_flags), |
| blend_mode(blend_mode), |
| opacity(opacity), |
| filter_quality(filter_quality) { |
| if (draw_region) { |
| this->draw_region.emplace(*draw_region); |
| } |
| } |
| |
| enum class SkiaRenderer::BypassMode { |
| // The RenderPass's contents' blendmode would have made a transparent black |
| // image and the RenderPass's own blend mode does not effect transparent black |
| kSkip, |
| // The renderPass's contents' creates a transparent image, but the |
| // RenderPass's own blend mode must still process the transparent pixels (e.g. |
| // certain filters affect transparent black). |
| kDrawTransparentQuad, |
| // Can draw the bypass quad with the modified parameters |
| kDrawBypassQuad |
| }; |
| |
| // Scoped helper class for building SkImage from resource id. |
| class SkiaRenderer::ScopedSkImageBuilder { |
| public: |
| ScopedSkImageBuilder(SkiaRenderer* skia_renderer, |
| ResourceId resource_id, |
| bool maybe_concurrent_reads, |
| SkAlphaType alpha_type = kPremul_SkAlphaType, |
| GrSurfaceOrigin origin = kTopLeft_GrSurfaceOrigin, |
| bool use_target_color_space = false); |
| ~ScopedSkImageBuilder() = default; |
| |
| const SkImage* sk_image() const { return sk_image_; } |
| |
| private: |
| const SkImage* sk_image_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedSkImageBuilder); |
| }; |
| |
| SkiaRenderer::ScopedSkImageBuilder::ScopedSkImageBuilder( |
| SkiaRenderer* skia_renderer, |
| ResourceId resource_id, |
| bool maybe_concurrent_reads, |
| SkAlphaType alpha_type, |
| GrSurfaceOrigin origin, |
| bool use_target_color_space) { |
| if (!resource_id) |
| return; |
| auto* resource_provider = skia_renderer->resource_provider_; |
| DCHECK(IsTextureResource(resource_provider, resource_id)); |
| |
| gfx::ColorSpace color_space; |
| if (use_target_color_space) |
| color_space = skia_renderer->CurrentRenderPassColorSpace(); |
| |
| auto* image_context = skia_renderer->lock_set_for_external_use_->LockResource( |
| resource_id, maybe_concurrent_reads, /*is_video_plane=*/false, |
| color_space); |
| // |ImageContext::image| provides thread safety: (a) this ImageContext is |
| // only accessed by GPU thread after |image| is set and (b) the fields of |
| // ImageContext that are accessed by both compositor and GPU thread are no |
| // longer modified after |image| is set. |
| if (!image_context->has_image()) { |
| image_context->set_alpha_type(alpha_type); |
| image_context->set_origin(origin); |
| } |
| skia_renderer->skia_output_surface_->MakePromiseSkImage(image_context); |
| LOG_IF(ERROR, !image_context->has_image()) |
| << "Failed to create the promise sk image."; |
| sk_image_ = image_context->image().get(); |
| } |
| |
| class SkiaRenderer::ScopedYUVSkImageBuilder { |
| public: |
| ScopedYUVSkImageBuilder(SkiaRenderer* skia_renderer, |
| const YUVVideoDrawQuad* quad, |
| sk_sp<SkColorSpace> dst_color_space) { |
| DCHECK(IsTextureResource(skia_renderer->resource_provider_, |
| quad->y_plane_resource_id())); |
| DCHECK(IsTextureResource(skia_renderer->resource_provider_, |
| quad->u_plane_resource_id())); |
| DCHECK(IsTextureResource(skia_renderer->resource_provider_, |
| quad->v_plane_resource_id())); |
| DCHECK(quad->a_plane_resource_id() == kInvalidResourceId || |
| IsTextureResource(skia_renderer->resource_provider_, |
| quad->a_plane_resource_id())); |
| |
| // The image is always either NV12 or I420, possibly with a separate alpha |
| // plane. |
| SkYUVAInfo::PlaneConfig plane_config; |
| if (quad->a_plane_resource_id() == kInvalidResourceId) { |
| plane_config = quad->u_plane_resource_id() == quad->v_plane_resource_id() |
| ? SkYUVAInfo::PlaneConfig::kY_UV |
| : SkYUVAInfo::PlaneConfig::kY_U_V; |
| } else { |
| plane_config = quad->u_plane_resource_id() == quad->v_plane_resource_id() |
| ? SkYUVAInfo::PlaneConfig::kY_UV_A |
| : SkYUVAInfo::PlaneConfig::kY_U_V_A; |
| } |
| const SkYUVAInfo::Subsampling subsampling = SkYUVAInfo::Subsampling::k420; |
| const int number_of_textures = SkYUVAInfo::NumPlanes(plane_config); |
| std::vector<ExternalUseClient::ImageContext*> contexts; |
| contexts.reserve(number_of_textures); |
| // Skia API ignores the color space information on the individual planes. |
| // Dropping them here avoids some LOG spam. |
| auto* y_context = skia_renderer->lock_set_for_external_use_->LockResource( |
| quad->y_plane_resource_id(), /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/true); |
| contexts.push_back(std::move(y_context)); |
| auto* u_context = skia_renderer->lock_set_for_external_use_->LockResource( |
| quad->u_plane_resource_id(), /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/true); |
| contexts.push_back(std::move(u_context)); |
| if (plane_config == SkYUVAInfo::PlaneConfig::kY_U_V || |
| plane_config == SkYUVAInfo::PlaneConfig::kY_U_V_A) { |
| auto* v_context = skia_renderer->lock_set_for_external_use_->LockResource( |
| quad->v_plane_resource_id(), /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/true); |
| contexts.push_back(std::move(v_context)); |
| } |
| |
| if (SkYUVAInfo::HasAlpha(plane_config)) { |
| auto* a_context = skia_renderer->lock_set_for_external_use_->LockResource( |
| quad->a_plane_resource_id(), /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/true); |
| contexts.push_back(std::move(a_context)); |
| } |
| |
| // Note: YUV to RGB and color conversion is handled by a color filter. |
| sk_image_ = skia_renderer->skia_output_surface_->MakePromiseSkImageFromYUV( |
| std::move(contexts), dst_color_space, plane_config, subsampling); |
| LOG_IF(ERROR, !sk_image_) << "Failed to create the promise sk yuva image."; |
| } |
| |
| ~ScopedYUVSkImageBuilder() = default; |
| |
| const SkImage* sk_image() const { return sk_image_.get(); } |
| |
| private: |
| std::unique_ptr<DisplayResourceProvider::ScopedReadLockSkImage> lock_; |
| sk_sp<SkImage> sk_image_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedYUVSkImageBuilder); |
| }; |
| |
| class SkiaRenderer::FrameResourceFence : public ResourceFence { |
| public: |
| FrameResourceFence() = default; |
| |
| // ResourceFence implementation. |
| void Set() override { set_ = true; } |
| bool HasPassed() override { return event_.IsSignaled(); } |
| |
| bool WasSet() { return set_; } |
| void Signal() { event_.Signal(); } |
| |
| private: |
| ~FrameResourceFence() override = default; |
| |
| // Accessed only from compositor thread. |
| bool set_ = false; |
| |
| base::WaitableEvent event_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FrameResourceFence); |
| }; |
| |
| SkiaRenderer::SkiaRenderer(const RendererSettings* settings, |
| const DebugRendererSettings* debug_settings, |
| OutputSurface* output_surface, |
| DisplayResourceProvider* resource_provider, |
| OverlayProcessorInterface* overlay_processor, |
| SkiaOutputSurface* skia_output_surface) |
| : DirectRenderer(settings, |
| debug_settings, |
| output_surface, |
| resource_provider, |
| overlay_processor), |
| skia_output_surface_(skia_output_surface) { |
| DCHECK(skia_output_surface_); |
| lock_set_for_external_use_.emplace(resource_provider, skia_output_surface_); |
| |
| current_frame_resource_fence_ = base::MakeRefCounted<FrameResourceFence>(); |
| resource_provider_->SetReadLockFence(current_frame_resource_fence_.get()); |
| } |
| |
| SkiaRenderer::~SkiaRenderer() = default; |
| |
| bool SkiaRenderer::CanPartialSwap() { |
| return output_surface_->capabilities().supports_post_sub_buffer; |
| } |
| |
| void SkiaRenderer::BeginDrawingFrame() { |
| TRACE_EVENT0("viz", "SkiaRenderer::BeginDrawingFrame"); |
| |
| DCHECK(!current_frame_resource_fence_->WasSet()); |
| |
| #if defined(OS_ANDROID) |
| for (const auto& pass : *current_frame()->render_passes_in_draw_order) { |
| for (auto* quad : pass->quad_list) { |
| for (ResourceId resource_id : quad->resources) |
| resource_provider_->InitializePromotionHintRequest(resource_id); |
| } |
| } |
| #endif |
| } |
| |
| void SkiaRenderer::FinishDrawingFrame() { |
| TRACE_EVENT0("viz", "SkiaRenderer::FinishDrawingFrame"); |
| current_canvas_ = nullptr; |
| current_surface_ = nullptr; |
| |
| swap_buffer_rect_ = current_frame()->root_damage_rect; |
| |
| if (use_swap_with_bounds_) |
| swap_content_bounds_ = current_frame()->root_content_bounds; |
| |
| // TODO(weiliangc): Remove this once OverlayProcessor schedules overlays. |
| if (current_frame()->output_surface_plane) { |
| skia_output_surface_->ScheduleOutputSurfaceAsOverlay( |
| current_frame()->output_surface_plane.value()); |
| } |
| ScheduleOverlays(); |
| } |
| |
| void SkiaRenderer::SwapBuffers(SwapFrameData swap_frame_data) { |
| DCHECK(visible_); |
| TRACE_EVENT0("viz,benchmark", "SkiaRenderer::SwapBuffers"); |
| OutputSurfaceFrame output_frame; |
| output_frame.latency_info = std::move(swap_frame_data.latency_info); |
| output_frame.top_controls_visible_height_changed = |
| swap_frame_data.top_controls_visible_height_changed; |
| output_frame.size = surface_size_for_swap_buffers(); |
| if (use_swap_with_bounds_) { |
| output_frame.content_bounds = std::move(swap_content_bounds_); |
| } else if (use_partial_swap_) { |
| swap_buffer_rect_.Intersect(gfx::Rect(surface_size_for_swap_buffers())); |
| output_frame.sub_buffer_rect = swap_buffer_rect_; |
| } else if (swap_buffer_rect_.IsEmpty() && allow_empty_swap_) { |
| output_frame.sub_buffer_rect = swap_buffer_rect_; |
| } |
| |
| skia_output_surface_->SwapBuffers(std::move(output_frame)); |
| swap_buffer_rect_ = gfx::Rect(); |
| |
| FlushOutputSurface(); |
| } |
| |
| void SkiaRenderer::SwapBuffersSkipped() { |
| skia_output_surface_->SwapBuffersSkipped(); |
| FlushOutputSurface(); |
| } |
| |
| void SkiaRenderer::SwapBuffersComplete() { |
| // Right now, only macOS needs to return mailboxes of released overlays, so |
| // we should not release |committed_overlay_locks_| here. The resources in it |
| // will be released by DidReceiveReleasedOverlays() later. |
| #if defined(OS_APPLE) |
| for (auto& lock : committed_overlay_locks_) { |
| awaiting_release_overlay_locks_.insert(std::move(lock)); |
| } |
| #endif // defined(OS_APPLE) |
| |
| committed_overlay_locks_.clear(); |
| std::swap(committed_overlay_locks_, pending_overlay_locks_.front()); |
| pending_overlay_locks_.pop_front(); |
| } |
| |
| void SkiaRenderer::DidReceiveReleasedOverlays( |
| const std::vector<gpu::Mailbox>& released_overlays) { |
| // This method is only called on macOS right now. |
| #if defined(OS_APPLE) |
| for (const auto& mailbox : released_overlays) { |
| auto it = awaiting_release_overlay_locks_.find(mailbox); |
| if (it == awaiting_release_overlay_locks_.end()) { |
| DLOG(FATAL) << "Got an unexpected mailbox"; |
| continue; |
| } |
| awaiting_release_overlay_locks_.erase(it); |
| } |
| #else |
| NOTREACHED(); |
| #endif // !defined(OS_APPLE) |
| } |
| |
| bool SkiaRenderer::FlippedFramebuffer() const { |
| // TODO(weiliangc): Make sure flipped correctly for Windows. |
| // (crbug.com/644851) |
| return false; |
| } |
| |
| void SkiaRenderer::EnsureScissorTestEnabled() { |
| is_scissor_enabled_ = true; |
| } |
| |
| void SkiaRenderer::EnsureScissorTestDisabled() { |
| is_scissor_enabled_ = false; |
| } |
| |
| void SkiaRenderer::BindFramebufferToOutputSurface() { |
| DCHECK(!output_surface_->HasExternalStencilTest()); |
| |
| root_canvas_ = skia_output_surface_->BeginPaintCurrentFrame(); |
| current_canvas_ = root_canvas_; |
| current_surface_ = root_surface_.get(); |
| } |
| |
| void SkiaRenderer::BindFramebufferToTexture( |
| const AggregatedRenderPassId render_pass_id) { |
| auto iter = render_pass_backings_.find(render_pass_id); |
| DCHECK(render_pass_backings_.end() != iter); |
| // This function is called after AllocateRenderPassResourceIfNeeded, so there |
| // should be backing ready. |
| RenderPassBacking& backing = iter->second; |
| current_canvas_ = skia_output_surface_->BeginPaintRenderPass( |
| render_pass_id, backing.size, backing.format, backing.generate_mipmap, |
| backing.color_space.ToSkColorSpace()); |
| } |
| |
| void SkiaRenderer::SetScissorTestRect(const gfx::Rect& scissor_rect) { |
| is_scissor_enabled_ = true; |
| scissor_rect_ = scissor_rect; |
| } |
| |
| void SkiaRenderer::ClearCanvas(SkColor color) { |
| if (!current_canvas_) |
| return; |
| |
| if (is_scissor_enabled_) { |
| // Limit the clear with the scissor rect. |
| SkAutoCanvasRestore autoRestore(current_canvas_, true /* do_save */); |
| current_canvas_->clipRect(gfx::RectToSkRect(scissor_rect_)); |
| current_canvas_->clear(color); |
| } else { |
| current_canvas_->clear(color); |
| } |
| } |
| |
| void SkiaRenderer::ClearFramebuffer() { |
| if (current_frame()->current_render_pass->has_transparent_background) { |
| ClearCanvas(SkColorSetARGB(0, 0, 0, 0)); |
| } else { |
| #if DCHECK_IS_ON() |
| // On DEBUG builds, opaque render passes are cleared to blue |
| // to easily see regions that were not drawn on the screen. |
| ClearCanvas(SkColorSetARGB(255, 0, 0, 255)); |
| #endif |
| } |
| } |
| |
| void SkiaRenderer::PrepareSurfaceForPass( |
| SurfaceInitializationMode initialization_mode, |
| const gfx::Rect& render_pass_scissor) { |
| switch (initialization_mode) { |
| case SURFACE_INITIALIZATION_MODE_PRESERVE: |
| EnsureScissorTestDisabled(); |
| return; |
| case SURFACE_INITIALIZATION_MODE_FULL_SURFACE_CLEAR: |
| EnsureScissorTestDisabled(); |
| ClearFramebuffer(); |
| break; |
| case SURFACE_INITIALIZATION_MODE_SCISSORED_CLEAR: |
| SetScissorTestRect(render_pass_scissor); |
| ClearFramebuffer(); |
| break; |
| } |
| } |
| |
| void SkiaRenderer::DoDrawQuad(const DrawQuad* quad, |
| const gfx::QuadF* draw_region) { |
| if (!current_canvas_) |
| return; |
| TRACE_EVENT0("viz", "SkiaRenderer::DoDrawQuad"); |
| gfx::Transform target_to_device = |
| current_frame()->window_matrix * current_frame()->projection_matrix; |
| const gfx::Rect* scissor = is_scissor_enabled_ ? &scissor_rect_ : nullptr; |
| DrawQuadParams params = |
| CalculateDrawQuadParams(target_to_device, scissor, quad, draw_region); |
| // The outer DrawQuad will never have RPDQ params |
| DrawQuadInternal(quad, /* rpdq */ nullptr, ¶ms); |
| } |
| |
| void SkiaRenderer::DrawQuadInternal(const DrawQuad* quad, |
| const DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) { |
| if (MustFlushBatchedQuads(quad, rpdq_params, *params)) { |
| FlushBatchedQuads(); |
| } |
| |
| switch (quad->material) { |
| case DrawQuad::Material::kAggregatedRenderPass: |
| // RPDQ should only ever be encountered as a top-level quad, not when |
| // bypassing another renderpass |
| DCHECK(rpdq_params == nullptr); |
| DrawRenderPassQuad(AggregatedRenderPassDrawQuad::MaterialCast(quad), |
| params); |
| break; |
| case DrawQuad::Material::kDebugBorder: |
| // DebugBorders draw directly into the device space, so are not compatible |
| // with image filters, so should never have been promoted as a bypass quad |
| DCHECK(rpdq_params == nullptr); |
| DrawDebugBorderQuad(DebugBorderDrawQuad::MaterialCast(quad), params); |
| break; |
| case DrawQuad::Material::kPictureContent: |
| // PictureQuads represent a collection of drawn elements that are |
| // dynamically rasterized by Skia; bypassing a RenderPass to redraw the |
| // N elements doesn't make much sense. |
| DCHECK(rpdq_params == nullptr); |
| DrawPictureQuad(PictureDrawQuad::MaterialCast(quad), params); |
| break; |
| case DrawQuad::Material::kCompositorRenderPass: |
| // RenderPassDrawQuads should be converted to |
| // AggregatedRenderPassDrawQuads at this point. |
| DrawUnsupportedQuad(quad, rpdq_params, params); |
| NOTREACHED(); |
| break; |
| case DrawQuad::Material::kSolidColor: |
| DrawSolidColorQuad(SolidColorDrawQuad::MaterialCast(quad), rpdq_params, |
| params); |
| break; |
| case DrawQuad::Material::kStreamVideoContent: |
| DrawStreamVideoQuad(StreamVideoDrawQuad::MaterialCast(quad), rpdq_params, |
| params); |
| break; |
| case DrawQuad::Material::kTextureContent: |
| DrawTextureQuad(TextureDrawQuad::MaterialCast(quad), rpdq_params, params); |
| break; |
| case DrawQuad::Material::kTiledContent: |
| DrawTileDrawQuad(TileDrawQuad::MaterialCast(quad), rpdq_params, params); |
| break; |
| case DrawQuad::Material::kYuvVideoContent: |
| DrawYUVVideoQuad(YUVVideoDrawQuad::MaterialCast(quad), rpdq_params, |
| params); |
| break; |
| case DrawQuad::Material::kInvalid: |
| DrawUnsupportedQuad(quad, rpdq_params, params); |
| NOTREACHED(); |
| break; |
| case DrawQuad::Material::kVideoHole: |
| // VideoHoleDrawQuad should only be used by Cast, and should |
| // have been replaced by cast-specific OverlayProcessor before |
| // reach here. In non-cast build, an untrusted render could send such |
| // Quad and the quad would then reach here unexpectedly. Therefore |
| // we should skip NOTREACHED() so an untrusted render is not capable |
| // of causing a crash. |
| DrawUnsupportedQuad(quad, rpdq_params, params); |
| break; |
| default: |
| // If we've reached here, it's a new quad type that needs a |
| // dedicated implementation |
| DrawUnsupportedQuad(quad, rpdq_params, params); |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void SkiaRenderer::PrepareCanvas( |
| const base::Optional<gfx::Rect>& scissor_rect, |
| const base::Optional<gfx::RRectF>& rounded_corner_bounds, |
| const gfx::Transform* cdt) { |
| // Scissor is applied in the device space (CTM == I) and since no changes |
| // to the canvas persist, CTM should already be the identity |
| DCHECK(current_canvas_->getTotalMatrix() == SkMatrix::I()); |
| |
| if (scissor_rect.has_value()) { |
| current_canvas_->clipRect(gfx::RectToSkRect(*scissor_rect)); |
| } |
| |
| if (rounded_corner_bounds.has_value()) |
| current_canvas_->clipRRect(SkRRect(*rounded_corner_bounds), true /* AA */); |
| |
| if (cdt) { |
| SkMatrix m; |
| gfx::TransformToFlattenedSkMatrix(*cdt, &m); |
| current_canvas_->concat(m); |
| } |
| } |
| |
| void SkiaRenderer::PrepareCanvasForRPDQ(const DrawRPDQParams& rpdq_params, |
| DrawQuadParams* params) { |
| // Clip to the filter bounds prior to saving the layer, which has been |
| // constructed to contain the actual filtered contents (visually no |
| // clipping effect, but lets Skia minimize internal layer size). |
| bool aa = params->aa_flags != SkCanvas::kNone_QuadAAFlags; |
| current_canvas_->clipRect(gfx::RectToSkRect(rpdq_params.filter_bounds), aa); |
| |
| SkPaint layer_paint = params->paint(nullptr /* color_filter */); |
| // The layer always consumes the opacity, but its blend mode depends on if |
| // it was initialized with backdrop content or not. |
| params->opacity = 1.f; |
| if (rpdq_params.backdrop_filter) { |
| layer_paint.setBlendMode(SkBlendMode::kSrcOver); |
| } else { |
| params->blend_mode = SkBlendMode::kSrcOver; |
| } |
| |
| if (rpdq_params.color_filter) { |
| layer_paint.setColorFilter(rpdq_params.color_filter); |
| } else if (rpdq_params.image_filter) { |
| layer_paint.setImageFilter(rpdq_params.image_filter); |
| } |
| |
| // Canocalize the backdrop bounds rrect type; if there's no backdrop filter or |
| // filter bounds, this will be empty. If it's a rect or rrect, we must work |
| // around Skia's background filter auto-expansion. If it's an rrect, we must |
| // also clear out the rounded corners after filtering. |
| gfx::RRectF::Type backdrop_bounds_type = gfx::RRectF::Type::kEmpty; |
| if (rpdq_params.backdrop_filter && |
| rpdq_params.backdrop_filter_bounds.has_value()) { |
| backdrop_bounds_type = rpdq_params.backdrop_filter_bounds->GetType(); |
| } |
| |
| // Initially the backdrop filter fills the entire rect; if we draw less than |
| // that we need to clear the excess. |
| bool post_backdrop_filter_clear_needed = params->draw_region.has_value(); |
| |
| // Explicitly crop the input and the output to the backdrop bounds; this is |
| // required for the backdrop-filter spec. |
| sk_sp<SkImageFilter> backdrop_filter = rpdq_params.backdrop_filter; |
| if (backdrop_bounds_type != gfx::RRectF::Type::kEmpty) { |
| DCHECK(backdrop_filter); |
| |
| gfx::Rect crop_rect = |
| gfx::ToEnclosingRect(rpdq_params.backdrop_filter_bounds->rect()); |
| SkIRect sk_crop_rect = gfx::RectToSkIRect(crop_rect); |
| |
| SkIRect sk_src_rect = backdrop_filter->filterBounds( |
| sk_crop_rect, SkMatrix::I(), SkImageFilter::kReverse_MapDirection, |
| &sk_crop_rect); |
| if (sk_crop_rect == sk_src_rect) { |
| // The backdrop filter does not "move" pixels, i.e. a pixel's value only |
| // depends on its (x,y) and prior color. Avoid cropping the input in this |
| // case since composing a crop rect into the filter DAG forces Skia to |
| // map the backdrop content into the local space, which can introduce |
| // filtering artifacts: crbug.com/1044032. Instead just post-filter |
| // clearing will achieve the same cropping of the output at higher quality |
| post_backdrop_filter_clear_needed = true; |
| } else { |
| // Offsetting (0,0) does nothing to the actual image, but is the most |
| // convenient way to embed the crop rect into the filter DAG. |
| // TODO(michaelludwig) - Remove this once Skia doesn't always auto-expand |
| sk_sp<SkImageFilter> crop = |
| SkImageFilters::Offset(0.0f, 0.0f, nullptr, &sk_crop_rect); |
| backdrop_filter = SkImageFilters::Compose( |
| crop, SkImageFilters::Compose(std::move(backdrop_filter), crop)); |
| // Update whether or not a post-filter clear is needed (crop didn't |
| // completely match bounds) |
| post_backdrop_filter_clear_needed |= |
| backdrop_bounds_type != gfx::RRectF::Type::kRect || |
| gfx::RectF(crop_rect) != rpdq_params.backdrop_filter_bounds->rect(); |
| } |
| } |
| |
| SkRect bounds = gfx::RectFToSkRect(rpdq_params.bypass_clip.has_value() |
| ? *rpdq_params.bypass_clip |
| : params->visible_rect); |
| current_canvas_->saveLayer( |
| SkCanvas::SaveLayerRec(&bounds, &layer_paint, backdrop_filter.get(), 0)); |
| |
| // If we have backdrop filtered content (and not transparent black like with |
| // regular render passes), we have to clear out the parts of the layer that |
| // shouldn't show the backdrop |
| if (backdrop_filter && post_backdrop_filter_clear_needed) { |
| current_canvas_->save(); |
| if (rpdq_params.backdrop_filter_bounds.has_value()) { |
| current_canvas_->clipRRect(SkRRect(*rpdq_params.backdrop_filter_bounds), |
| SkClipOp::kDifference, aa); |
| } |
| if (params->draw_region.has_value()) { |
| SkPath clipPath; |
| clipPath.addPoly(params->draw_region->points, 4, true /* close */); |
| if (rpdq_params.bypass_transform.has_value()) { |
| clipPath.transform(*rpdq_params.bypass_transform); |
| } |
| current_canvas_->clipPath(clipPath, SkClipOp::kDifference, aa); |
| } |
| current_canvas_->clear(SK_ColorTRANSPARENT); |
| current_canvas_->restore(); |
| } |
| } |
| |
| void SkiaRenderer::PreparePaintOrCanvasForRPDQ( |
| const DrawRPDQParams& rpdq_params, |
| DrawQuadParams* params, |
| SkPaint* paint) { |
| // When the draw call accepts an SkPaint, some of the rpdq effects are more |
| // efficient to store on the paint instead of making an explicit layer in |
| // the canvas. But there are several requirements in order for the order of |
| // operations to be consistent with what RenderPasses require: |
| // 1. Backdrop filtering always requires a layer. |
| // 2. The content bypassing the renderpass needs to be clipped before the |
| // image filter is evaluated. |
| bool needs_bypass_clip = rpdq_params.needs_bypass_clip(params->visible_rect); |
| bool needs_save_layer = false; |
| if (rpdq_params.backdrop_filter) |
| needs_save_layer = true; |
| else if (rpdq_params.has_complex_image_filter()) |
| needs_save_layer = needs_bypass_clip; |
| |
| if (rpdq_params.mask_shader) { |
| // Apply the mask image using clipShader(), this works the same regardless |
| // of if we need a saveLayer for image filtering since the clip is applied |
| // at the end automatically. |
| current_canvas_->clipShader(rpdq_params.mask_shader); |
| } |
| |
| if (needs_save_layer) { |
| PrepareCanvasForRPDQ(rpdq_params, params); |
| // Sync the content's paint with the updated |params| |
| paint->setAlphaf(params->opacity); |
| paint->setBlendMode(params->blend_mode); |
| } else { |
| // At this point, the image filter and/or color filter are on the paint. |
| DCHECK(!rpdq_params.backdrop_filter); |
| if (rpdq_params.color_filter) { |
| // Use the color filter directly, instead of the image filter. |
| if (paint->getColorFilter()) { |
| paint->setColorFilter( |
| rpdq_params.color_filter->makeComposed(paint->refColorFilter())); |
| } else { |
| paint->setColorFilter(rpdq_params.color_filter); |
| } |
| DCHECK(paint->getColorFilter()); |
| } else if (rpdq_params.image_filter) { |
| // Store the image filter on the paint. |
| if (params->opacity != 1.f) { |
| // Apply opacity as the last step of image filter so it is uniform |
| // across any overlapping content produced by the image filters. |
| paint->setImageFilter(SkColorFilterImageFilter::Make( |
| MakeOpacityFilter(params->opacity, nullptr), |
| rpdq_params.image_filter)); |
| paint->setAlphaf(1.f); |
| params->opacity = 1.f; |
| } else { |
| paint->setImageFilter(rpdq_params.image_filter); |
| } |
| } |
| } |
| |
| // Whether or not we saved a layer, clip the bypassed RenderPass's content |
| if (needs_bypass_clip) { |
| current_canvas_->clipRect(gfx::RectFToSkRect(*rpdq_params.bypass_clip), |
| params->aa_flags != SkCanvas::kNone_QuadAAFlags); |
| } |
| } |
| |
| void SkiaRenderer::PrepareColorOrCanvasForRPDQ( |
| const DrawRPDQParams& rpdq_params, |
| DrawQuadParams* params, |
| SkColor* content_color) { |
| // When the draw call only takes a color and not an SkPaint, rpdq params |
| // with just a color filter can be handled directly. Otherwise, the rpdq |
| // params must use a layer on the canvas. |
| bool needs_save_layer = |
| rpdq_params.has_complex_image_filter() || rpdq_params.backdrop_filter; |
| if (rpdq_params.mask_shader) { |
| current_canvas_->clipShader(rpdq_params.mask_shader); |
| } |
| |
| if (needs_save_layer) { |
| PrepareCanvasForRPDQ(rpdq_params, params); |
| } else if (rpdq_params.color_filter) { |
| // At this point, the RPDQ effect is at most a color filter, so it can |
| // modify |content_color| directly. |
| *content_color = rpdq_params.color_filter->filterColor(*content_color); |
| } |
| |
| // Even if the color filter image filter was applied to the content color |
| // directly (so no explicit save layer), the draw may need to be clipped to |
| // the output rect of the renderpass it is bypassing. |
| if (rpdq_params.needs_bypass_clip(params->visible_rect)) { |
| current_canvas_->clipRect(gfx::RectFToSkRect(*rpdq_params.bypass_clip), |
| params->aa_flags != SkCanvas::kNone_QuadAAFlags); |
| } |
| } |
| |
| SkiaRenderer::DrawQuadParams SkiaRenderer::CalculateDrawQuadParams( |
| const gfx::Transform& target_to_device, |
| const gfx::Rect* scissor_rect, |
| const DrawQuad* quad, |
| const gfx::QuadF* draw_region) const { |
| DrawQuadParams params( |
| target_to_device * quad->shared_quad_state->quad_to_target_transform, |
| gfx::RectF(quad->visible_rect), SkCanvas::kNone_QuadAAFlags, |
| quad->shared_quad_state->blend_mode, quad->shared_quad_state->opacity, |
| GetFilterQuality(quad), draw_region); |
| |
| params.content_device_transform.FlattenTo2d(); |
| |
| // Respect per-quad setting overrides as highest priority setting |
| if (!IsAAForcedOff(quad)) { |
| if (settings_->force_antialiasing) { |
| // This setting makes the entire draw AA, so don't bother checking edges |
| params.aa_flags = SkCanvas::kAll_QuadAAFlags; |
| } else if (settings_->allow_antialiasing) { |
| params.aa_flags = GetRectilinearEdgeFlags(quad); |
| if (draw_region && params.aa_flags != SkCanvas::kNone_QuadAAFlags) { |
| // Turn off interior edges' AA from the BSP splitting |
| GetClippedEdgeFlags(quad, ¶ms.aa_flags, &*params.draw_region); |
| } |
| } |
| } |
| |
| if (!quad->ShouldDrawWithBlending()) { |
| // The quad layer is src-over with 1.0 opacity and its needs_blending flag |
| // has been set to false. However, even if the layer's opacity is 1.0, the |
| // contents may not be (e.g. png or a color with alpha). |
| if (quad->shared_quad_state->are_contents_opaque) { |
| // Visually, this is the same as kSrc but Skia is faster with SrcOver |
| params.blend_mode = SkBlendMode::kSrcOver; |
| } else { |
| // Replaces dst contents with the new color (e.g. no blending); this is |
| // just as fast as srcover when there's no AA, but is slow when coverage |
| // must be taken into account. |
| params.blend_mode = SkBlendMode::kSrc; |
| } |
| params.opacity = 1.f; |
| } |
| |
| // Applying the scissor explicitly means avoiding a clipRect() call and |
| // allows more quads to be batched together in a DrawEdgeAAImageSet call |
| if (scissor_rect) { |
| if (CanExplicitlyScissor(quad, draw_region, |
| params.content_device_transform)) { |
| ApplyExplicitScissor(quad, *scissor_rect, params.content_device_transform, |
| ¶ms.aa_flags, ¶ms.visible_rect); |
| params.vis_tex_coords = params.visible_rect; |
| } else { |
| params.scissor_rect = *scissor_rect; |
| } |
| } |
| |
| // Determine final rounded rect clip geometry. We transform it from target |
| // space to window space to make batching and canvas preparation easier |
| // (otherwise we'd have to separate those two matrices in the CDT). |
| if (ShouldApplyRoundedCorner(quad)) { |
| // Transform by the window and projection matrix to go from target to |
| // device space, which should always be a scale+translate. |
| SkRRect corner_bounds = SkRRect( |
| quad->shared_quad_state->mask_filter_info.rounded_corner_bounds()); |
| SkMatrix to_device; |
| gfx::TransformToFlattenedSkMatrix(target_to_device, &to_device); |
| |
| SkRRect device_bounds; |
| bool success = corner_bounds.transform(to_device, &device_bounds); |
| // Since to_device should just be scale+translate, transform always succeeds |
| DCHECK(success); |
| if (!device_bounds.isEmpty()) { |
| params.rounded_corner_bounds.emplace(device_bounds); |
| } |
| } |
| |
| return params; |
| } |
| |
| bool SkiaRenderer::CanExplicitlyScissor( |
| const DrawQuad* quad, |
| const gfx::QuadF* draw_region, |
| const gfx::Transform& contents_device_transform) const { |
| // PICTURE_CONTENT is not like the others, since it is executing a list of |
| // draw calls into the canvas. |
| if (quad->material == DrawQuad::Material::kPictureContent) |
| return false; |
| // Intersection with scissor and a quadrilateral is not necessarily a quad, |
| // so don't complicate things |
| if (draw_region) |
| return false; |
| |
| // This is slightly different than |
| // gfx::Transform::IsPositiveScaleAndTranslation in that it also allows zero |
| // scales. This is because in the common orthographic case the z scale is 0. |
| if (!contents_device_transform.IsScaleOrTranslation()) |
| return false; |
| |
| // Sanity check: we should not have a Compositor CompositorRenderPassDrawQuad |
| // here. |
| DCHECK_NE(quad->material, DrawQuad::Material::kCompositorRenderPass); |
| if (quad->material == DrawQuad::Material::kAggregatedRenderPass) { |
| // If the renderpass has filters, the filters may modify the effective |
| // geometry beyond the quad's visible_rect, so it's not safe to pre-clip. |
| auto pass_id = |
| AggregatedRenderPassDrawQuad::MaterialCast(quad)->render_pass_id; |
| if (FiltersForPass(pass_id) || BackdropFiltersForPass(pass_id)) |
| return false; |
| } |
| |
| return contents_device_transform.matrix().get(0, 0) >= 0.0 && |
| contents_device_transform.matrix().get(1, 1) >= 0.0 && |
| contents_device_transform.matrix().get(2, 2) >= 0.0; |
| } |
| |
| const DrawQuad* SkiaRenderer::CanPassBeDrawnDirectly( |
| const AggregatedRenderPass* pass) { |
| // TODO(michaelludwig) - For now, this only supports opaque, src-over quads |
| // with invertible transforms and simple content (image or color only). |
| // Can only collapse a single tile quad. |
| if (pass->quad_list.size() != 1) |
| return nullptr; |
| |
| // If it there are supposed to be mipmaps, the renderpass must exist |
| if (pass->generate_mipmap) |
| return nullptr; |
| |
| const DrawQuad* quad = *pass->quad_list.BackToFrontBegin(); |
| // For simplicity in their draw implementations, debug borders, picture quads, |
| // and nested render passes cannot bypass a render pass |
| // (their draw functions do not accept DrawRPDQParams either). |
| DCHECK_NE(quad->material, DrawQuad::Material::kCompositorRenderPass); |
| if (quad->material == DrawQuad::Material::kAggregatedRenderPass || |
| quad->material == DrawQuad::Material::kDebugBorder || |
| quad->material == DrawQuad::Material::kPictureContent) |
| return nullptr; |
| |
| // If the quad specifies nearest-neighbor scaling then there could be two |
| // scaling operations at different quality levels. This requires drawing to an |
| // intermediate render pass. See https://crbug.com/1155338. |
| if (UseNearestNeighborSampling(quad)) |
| return nullptr; |
| |
| if (quad->material == DrawQuad::Material::kTextureContent) { |
| // Per-vertex opacities complicate bypassing the RP and alpha blending the |
| // texture with image filters, so punt under that rare circumstance. |
| const TextureDrawQuad* tex = TextureDrawQuad::MaterialCast(quad); |
| if (tex->vertex_opacity[0] < 1.f || tex->vertex_opacity[1] < 1.f || |
| tex->vertex_opacity[2] < 1.f || tex->vertex_opacity[3] < 1.f) { |
| return nullptr; |
| } |
| } |
| |
| // In order to concatenate the bypass'ed quads transform with RP itself, it |
| // needs to be invertible. |
| if (!quad->shared_quad_state->quad_to_target_transform.IsInvertible()) |
| return nullptr; |
| |
| // A renderpass normally draws its content into a transparent destination, |
| // using the quad's blend mode, then that result is later drawn into the |
| // real dst with the RP's blend mode. In order to bypass the RP and draw |
| // correctly, CalculateBypassParams must be able to reason about the quad's |
| // blend mode. |
| if (!IsPorterDuffBlendMode(quad->shared_quad_state->blend_mode)) |
| return nullptr; |
| // All Porter-Duff blending with transparent black should fall into one of |
| // these two categories: |
| DCHECK(RenderPassPreservesContent(quad->shared_quad_state->blend_mode) || |
| RenderPassRemainsTransparent(quad->shared_quad_state->blend_mode)); |
| |
| // The content must not have any rrect clipping, since skia_renderer applies |
| // the rrect in device space, and in this case, the bypass quad's device space |
| // is the RP's buffer. |
| // TODO(michaelludwig) - If this becomes a bottleneck, we can track the |
| // bypass rrect separately and update PrepareCanvasForRDQP to apply the |
| // additional clip. |
| if (ShouldApplyRoundedCorner(quad)) |
| return nullptr; |
| |
| // The quad type knows how to apply RPDQ filters, and the quad settings can |
| // be merged into the RPDQs settings in CalculateBypassParams. |
| return quad; |
| } |
| |
| SkiaRenderer::BypassMode SkiaRenderer::CalculateBypassParams( |
| const DrawQuad* bypass_quad, |
| DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) const { |
| // Depending on bypass_quad's blend mode, its content may be irrelevant |
| if (RenderPassRemainsTransparent( |
| bypass_quad->shared_quad_state->blend_mode)) { |
| // NOTE: this uses the pass's blend mode since this refers to the final draw |
| // of the render pass itself |
| if (TransparentBlackAffectsOutput(params->blend_mode)) { |
| return BypassMode::kDrawTransparentQuad; |
| } else { |
| return BypassMode::kSkip; |
| } |
| } |
| // If we made it here, the bypass blend mode would have just preserved the |
| // bypass quad's content, so we can draw it directly using the render pass's |
| // blend mode instead. |
| DCHECK( |
| RenderPassPreservesContent(bypass_quad->shared_quad_state->blend_mode)); |
| |
| // The bypass quad will be drawn directly, so update |params| and |
| // |rpdq_params| to reflect the change of coordinate system and merge settings |
| // between the inner and outer quads. |
| SkMatrix rpdq_to_bypass; |
| SkMatrix bypass_to_rpdq; |
| gfx::TransformToFlattenedSkMatrix( |
| bypass_quad->shared_quad_state->quad_to_target_transform, |
| &bypass_to_rpdq); |
| bool inverted = bypass_to_rpdq.invert(&rpdq_to_bypass); |
| // Invertibility was a requirement for being bypassable. |
| DCHECK(inverted); |
| |
| if (params->draw_region.has_value()) { |
| // The draw region was determined by the RPDQ's geometry, so map the |
| // quadrilateral to the bypass'ed quad's coordinate space so that BSP |
| // splitting is still respected. |
| rpdq_to_bypass.mapPoints(params->draw_region->points, 4); |
| } |
| |
| // Compute draw params for the bypass quad from scratch, but since the |
| // bypass_quad would have originally been drawn into the RP, the |
| // target_to_device transform is the full transform of the RPDQ. Must also |
| // include the RP's output rect as part of the scissor rect, since it would |
| // have been clipped to the edges of the RP's offscreen buffer normally. |
| DrawQuadParams bypass_params = CalculateDrawQuadParams( |
| gfx::Transform() /* identity */, nullptr, bypass_quad, nullptr); |
| |
| // |params| already holds the correct |draw_region|, but must be updated to |
| // use the bypassed transform and geometry |
| rpdq_params->bypass_transform = bypass_to_rpdq; |
| rpdq_params->bypass_clip = params->visible_rect; |
| // NOTE: params |content_device_transform| remains that of the RPDQ to prepare |
| // the canvas' CTM to match what any image filters require. The above |
| // bypass_transform is then applied when drawing so that these updated |
| // coordinates are correctly transformed to device space. |
| params->visible_rect = bypass_params.visible_rect; |
| params->vis_tex_coords = bypass_params.vis_tex_coords; |
| |
| // Combine anti-aliasing policy (use AND so that any draw_region clipping |
| // is preserved). |
| params->aa_flags &= bypass_params.aa_flags; |
| |
| // Blending will use the top-level RPDQ blend mode, but factor in the |
| // content's opacity as well, since that would have normally been baked into |
| // the RP's buffer. |
| params->opacity *= bypass_params.opacity; |
| |
| // Take the highest quality filter, since this single draw will reflect the |
| // filtering decisions made both when drawing into the RP and when drawing the |
| // RP results itself. |
| if (bypass_params.filter_quality > params->filter_quality) |
| params->filter_quality = bypass_params.filter_quality; |
| |
| // Rounded corner bounds are in device space, which gets tricky when bypassing |
| // the device that the RP would have represented |
| DCHECK(!bypass_params.rounded_corner_bounds.has_value()); |
| return BypassMode::kDrawBypassQuad; |
| } |
| |
| SkCanvas::ImageSetEntry SkiaRenderer::MakeEntry( |
| const SkImage* image, |
| int matrix_index, |
| const DrawQuadParams& params) const { |
| return SkCanvas::ImageSetEntry( |
| {sk_ref_sp(image), gfx::RectFToSkRect(params.vis_tex_coords), |
| gfx::RectFToSkRect(params.visible_rect), matrix_index, params.opacity, |
| params.aa_flags, params.draw_region.has_value()}); |
| } |
| |
| SkCanvas::SrcRectConstraint SkiaRenderer::ResolveTextureConstraints( |
| const SkImage* image, |
| const gfx::RectF& valid_texel_bounds, |
| DrawQuadParams* params) const { |
| if (params->aa_flags == SkCanvas::kNone_QuadAAFlags && |
| params->filter_quality == kNone_SkFilterQuality) { |
| // Non-AA and no bilinear filtering so rendering won't filter outside the |
| // provided texture coordinates. |
| return SkCanvas::kFast_SrcRectConstraint; |
| } |
| |
| // Resolve texture coordinates against the valid content area of the image |
| SkCanvas::SrcRectConstraint constraint = |
| GetTextureConstraint(image, params->vis_tex_coords, valid_texel_bounds); |
| |
| // Skia clamps to the provided texture coordinates, not the content_area. If |
| // there is a mismatch, have to update the draw params to account for the new |
| // constraint |
| if (constraint == SkCanvas::kFast_SrcRectConstraint || |
| valid_texel_bounds == params->vis_tex_coords) { |
| return constraint; |
| } |
| |
| // To get |valid_texel_bounds| as the constraint, it must be sent as the tex |
| // coords. To draw the right shape, store |visible_rect| as the |draw_region| |
| // and change the visible rect so that the mapping from |visible_rect| to |
| // |valid_texel_bounds| causes |draw_region| to map to original |
| // |vis_tex_coords| |
| if (!params->draw_region.has_value()) { |
| params->draw_region.emplace(gfx::QuadF(params->visible_rect)); |
| } |
| |
| // Preserve the src-to-dst transformation for the padded texture coords |
| SkMatrix src_to_dst = SkMatrix::MakeRectToRect( |
| gfx::RectFToSkRect(params->vis_tex_coords), |
| gfx::RectFToSkRect(params->visible_rect), SkMatrix::kFill_ScaleToFit); |
| params->visible_rect = gfx::SkRectToRectF( |
| src_to_dst.mapRect(gfx::RectFToSkRect(valid_texel_bounds))); |
| params->vis_tex_coords = valid_texel_bounds; |
| |
| return SkCanvas::kStrict_SrcRectConstraint; |
| } |
| |
| bool SkiaRenderer::MustFlushBatchedQuads(const DrawQuad* new_quad, |
| const DrawRPDQParams* rpdq_params, |
| const DrawQuadParams& params) const { |
| if (batched_quads_.empty()) |
| return false; |
| |
| // If |new_quad| is the bypass quad for a renderpass with filters, it must be |
| // drawn by itself, regardless of if it could otherwise would've been batched. |
| if (rpdq_params) |
| return true; |
| |
| DCHECK_NE(new_quad->material, DrawQuad::Material::kCompositorRenderPass); |
| if (new_quad->material != DrawQuad::Material::kAggregatedRenderPass && |
| new_quad->material != DrawQuad::Material::kStreamVideoContent && |
| new_quad->material != DrawQuad::Material::kTextureContent && |
| new_quad->material != DrawQuad::Material::kTiledContent) |
| return true; |
| |
| if (batched_quad_state_.blend_mode != params.blend_mode || |
| batched_quad_state_.filter_quality != params.filter_quality) |
| return true; |
| |
| if (batched_quad_state_.scissor_rect != params.scissor_rect) { |
| return true; |
| } |
| |
| if (batched_quad_state_.rounded_corner_bounds != |
| params.rounded_corner_bounds) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void SkiaRenderer::AddQuadToBatch(const SkImage* image, |
| const gfx::RectF& valid_texel_bounds, |
| DrawQuadParams* params) { |
| SkCanvas::SrcRectConstraint constraint = |
| ResolveTextureConstraints(image, valid_texel_bounds, params); |
| // Last check for flushing the batch, since constraint can't be known until |
| // the last minute. |
| if (!batched_quads_.empty() && batched_quad_state_.constraint != constraint) { |
| FlushBatchedQuads(); |
| } |
| |
| // Configure batch state if it's the first |
| if (batched_quads_.empty()) { |
| batched_quad_state_.scissor_rect = params->scissor_rect; |
| batched_quad_state_.rounded_corner_bounds = params->rounded_corner_bounds; |
| batched_quad_state_.blend_mode = params->blend_mode; |
| batched_quad_state_.filter_quality = params->filter_quality; |
| batched_quad_state_.constraint = constraint; |
| } |
| DCHECK(batched_quad_state_.constraint == constraint); |
| |
| // Add entry, with optional clip quad and shared transform |
| if (params->draw_region.has_value()) { |
| for (int i = 0; i < 4; ++i) { |
| batched_draw_regions_.push_back(params->draw_region->points[i]); |
| } |
| } |
| |
| SkMatrix m; |
| gfx::TransformToFlattenedSkMatrix(params->content_device_transform, &m); |
| std::vector<SkMatrix>& cdts = batched_cdt_matrices_; |
| if (cdts.empty() || cdts[cdts.size() - 1] != m) { |
| cdts.push_back(m); |
| } |
| int matrix_index = cdts.size() - 1; |
| |
| batched_quads_.push_back(MakeEntry(image, matrix_index, *params)); |
| } |
| |
| void SkiaRenderer::FlushBatchedQuads() { |
| TRACE_EVENT0("viz", "SkiaRenderer::FlushBatchedQuads"); |
| |
| SkAutoCanvasRestore acr(current_canvas_, true /* do_save */); |
| PrepareCanvas(batched_quad_state_.scissor_rect, |
| batched_quad_state_.rounded_corner_bounds, nullptr); |
| |
| SkPaint paint; |
| sk_sp<SkColorFilter> color_filter = GetContentColorFilter(); |
| if (color_filter) |
| paint.setColorFilter(color_filter); |
| paint.setFilterQuality(batched_quad_state_.filter_quality); |
| paint.setBlendMode(batched_quad_state_.blend_mode); |
| |
| current_canvas_->experimental_DrawEdgeAAImageSet( |
| &batched_quads_.front(), batched_quads_.size(), |
| batched_draw_regions_.data(), &batched_cdt_matrices_.front(), &paint, |
| batched_quad_state_.constraint); |
| |
| batched_quads_.clear(); |
| batched_draw_regions_.clear(); |
| batched_cdt_matrices_.clear(); |
| } |
| |
| void SkiaRenderer::DrawColoredQuad(SkColor color, |
| const DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) { |
| DCHECK(batched_quads_.empty()); |
| TRACE_EVENT0("viz", "SkiaRenderer::DrawColoredQuad"); |
| |
| SkAutoCanvasRestore acr(current_canvas_, true /* do_save */); |
| PrepareCanvas(params->scissor_rect, params->rounded_corner_bounds, |
| ¶ms->content_device_transform); |
| |
| if (rpdq_params) { |
| // This will modify the provided content color as needed for the RP effects, |
| // or it will make an explicit save layer on the current canvas |
| PrepareColorOrCanvasForRPDQ(*rpdq_params, params, &color); |
| if (rpdq_params->bypass_transform.has_value()) { |
| // Concatenate the bypass'ed quad's transform after all the RPDQ state |
| // has been pushed to the canvas. |
| current_canvas_->concat(*rpdq_params->bypass_transform); |
| } |
| } |
| |
| sk_sp<SkColorFilter> color_filter = GetContentColorFilter(); |
| if (color_filter) |
| color = color_filter->filterColor(color); |
| // PrepareCanvasForRPDQ will have updated params->opacity and blend_mode to |
| // account for the layer applying those effects. |
| color = SkColorSetA(color, params->opacity * SkColorGetA(color)); |
| |
| const SkPoint* draw_region = |
| params->draw_region.has_value() ? params->draw_region->points : nullptr; |
| |
| current_canvas_->experimental_DrawEdgeAAQuad( |
| gfx::RectFToSkRect(params->visible_rect), draw_region, |
| static_cast<SkCanvas::QuadAAFlags>(params->aa_flags), |
| SkColor4f::FromColor(color), params->blend_mode); |
| } |
| |
| void SkiaRenderer::DrawSingleImage(const SkImage* image, |
| const gfx::RectF& valid_texel_bounds, |
| const DrawRPDQParams* rpdq_params, |
| SkPaint* paint, |
| DrawQuadParams* params) { |
| DCHECK(batched_quads_.empty()); |
| TRACE_EVENT0("viz", "SkiaRenderer::DrawSingleImage"); |
| |
| SkAutoCanvasRestore acr(current_canvas_, true /* do_save */); |
| PrepareCanvas(params->scissor_rect, params->rounded_corner_bounds, |
| ¶ms->content_device_transform); |
| |
| int matrix_index = -1; |
| const SkMatrix* bypass_transform = nullptr; |
| if (rpdq_params) { |
| // This will modify the provided content paint as needed for the RP effects, |
| // or it will make an explicit save layer on the current canvas |
| PreparePaintOrCanvasForRPDQ(*rpdq_params, params, paint); |
| if (rpdq_params->bypass_transform.has_value()) { |
| // Incorporate the bypass transform, but unlike for solid color quads, do |
| // not modify the SkCanvas's CTM. This is because the RPDQ's filters may |
| // have been optimally placed on the SkPaint of the draw, which means the |
| // canvas' transform must match that of the RenderPass. The pre-CTM matrix |
| // of the image set entry can be used instead to modify the drawn geometry |
| // without impacting the filter's coordinate space. |
| bypass_transform = &(*rpdq_params->bypass_transform); |
| matrix_index = 0; |
| } |
| } |
| |
| // At this point, the params' opacity should be handled by |paint| (either |
| // as its alpha or in a color filter), or in an image filter from the RPDQ, |
| // or in the saved layer for the RPDQ. Set opacity to 1 to ensure the image |
| // set entry does not doubly-apply the opacity then. |
| params->opacity = 1.f; |
| |
| SkCanvas::SrcRectConstraint constraint = |
| ResolveTextureConstraints(image, valid_texel_bounds, params); |
| |
| // Use -1 for matrix index since the cdt is set on the canvas. |
| SkCanvas::ImageSetEntry entry = MakeEntry(image, matrix_index, *params); |
| const SkPoint* draw_region = |
| params->draw_region.has_value() ? params->draw_region->points : nullptr; |
| current_canvas_->experimental_DrawEdgeAAImageSet( |
| &entry, 1, draw_region, bypass_transform, paint, constraint); |
| } |
| |
| void SkiaRenderer::DrawDebugBorderQuad(const DebugBorderDrawQuad* quad, |
| DrawQuadParams* params) { |
| DCHECK(batched_quads_.empty()); |
| |
| SkAutoCanvasRestore acr(current_canvas_, true /* do_save */); |
| // We need to apply the matrix manually to have pixel-sized stroke width. |
| PrepareCanvas(params->scissor_rect, params->rounded_corner_bounds, nullptr); |
| SkMatrix cdt; |
| gfx::TransformToFlattenedSkMatrix(params->content_device_transform, &cdt); |
| |
| SkPath path; |
| if (params->draw_region.has_value()) { |
| path.addPoly(params->draw_region->points, 4, true /* close */); |
| } else { |
| path.addRect(gfx::RectFToSkRect(params->visible_rect)); |
| } |
| path.transform(cdt); |
| |
| SkPaint paint = params->paint(nullptr /* color_filter */); |
| paint.setColor(quad->color); // Must correct alpha afterwards |
| paint.setAlphaf(params->opacity * paint.getAlphaf()); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setStrokeJoin(SkPaint::kMiter_Join); |
| paint.setStrokeWidth(quad->width); |
| |
| current_canvas_->drawPath(path, paint); |
| } |
| |
| void SkiaRenderer::DrawPictureQuad(const PictureDrawQuad* quad, |
| DrawQuadParams* params) { |
| DCHECK(batched_quads_.empty()); |
| TRACE_EVENT0("viz", "SkiaRenderer::DrawPictureQuad"); |
| |
| // If the layer is transparent or needs a non-SrcOver blend mode, saveLayer |
| // must be used so that the display list is drawn into a transient image and |
| // then blended as a single layer at the end. |
| const bool needs_transparency = |
| params->opacity < 1.f || params->blend_mode != SkBlendMode::kSrcOver; |
| const bool disable_image_filtering = |
| disable_picture_quad_image_filtering_ || |
| params->filter_quality == kNone_SkFilterQuality; |
| |
| SkAutoCanvasRestore acr(current_canvas_, true /* do_save */); |
| PrepareCanvas(params->scissor_rect, params->rounded_corner_bounds, |
| ¶ms->content_device_transform); |
| |
| // Unlike other quads which draw visible_rect or draw_region as their geometry |
| // these represent the valid windows of content to show for the display list, |
| // so they need to be used as a clip in Skia. |
| SkRect visible_rect = gfx::RectFToSkRect(params->visible_rect); |
| SkPaint paint = params->paint(GetContentColorFilter()); |
| |
| if (params->draw_region.has_value()) { |
| SkPath clip; |
| clip.addPoly(params->draw_region->points, 4, true /* close */); |
| current_canvas_->clipPath(clip, paint.isAntiAlias()); |
| } else { |
| current_canvas_->clipRect(visible_rect, paint.isAntiAlias()); |
| } |
| |
| if (needs_transparency) { |
| // Use the DrawQuadParams' paint for the layer, since that will affect the |
| // final draw of the backing image. |
| current_canvas_->saveLayer(&visible_rect, &paint); |
| } |
| |
| SkCanvas* raster_canvas = current_canvas_; |
| base::Optional<skia::OpacityFilterCanvas> opacity_canvas; |
| if (disable_image_filtering) { |
| // TODO(vmpstr): Fold this canvas into playback and have raster source |
| // accept a set of settings on playback that will determine which canvas to |
| // apply. (http://crbug.com/594679) |
| // saveLayer applies the opacity, this filter is only used for quality |
| // overriding in the display list, hence the fixed 1.f for alpha. |
| opacity_canvas.emplace(raster_canvas, 1.f, disable_image_filtering); |
| raster_canvas = &*opacity_canvas; |
| } |
| |
| // Treat all subnormal values as zero for performance. |
| cc::ScopedSubnormalFloatDisabler disabler; |
| |
| raster_canvas->concat(SkMatrix::MakeRectToRect( |
| gfx::RectFToSkRect(quad->tex_coord_rect), gfx::RectToSkRect(quad->rect), |
| SkMatrix::kFill_ScaleToFit)); |
| |
| raster_canvas->translate(-quad->content_rect.x(), -quad->content_rect.y()); |
| raster_canvas->clipRect(gfx::RectToSkRect(quad->content_rect)); |
| raster_canvas->scale(quad->contents_scale, quad->contents_scale); |
| quad->display_item_list->Raster(raster_canvas); |
| } |
| |
| void SkiaRenderer::DrawSolidColorQuad(const SolidColorDrawQuad* quad, |
| const DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) { |
| DrawColoredQuad(quad->color, rpdq_params, params); |
| } |
| |
| void SkiaRenderer::DrawStreamVideoQuad(const StreamVideoDrawQuad* quad, |
| const DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) { |
| DCHECK(!MustFlushBatchedQuads(quad, rpdq_params, *params)); |
| |
| ScopedSkImageBuilder builder(this, quad->resource_id(), |
| /*maybe_concurrent_reads=*/true, |
| kUnpremul_SkAlphaType); |
| const SkImage* image = builder.sk_image(); |
| if (!image) |
| return; |
| |
| gfx::RectF uv_rect = gfx::ScaleRect( |
| gfx::BoundingRect(quad->uv_top_left, quad->uv_bottom_right), |
| image->width(), image->height()); |
| params->vis_tex_coords = cc::MathUtil::ScaleRectProportional( |
| uv_rect, gfx::RectF(quad->rect), params->visible_rect); |
| |
| // Use provided resource size if not empty, otherwise use the full image size |
| // as the content area |
| gfx::RectF valid_texel_bounds = |
| quad->resource_size_in_pixels().IsEmpty() |
| ? gfx::RectF(image->width(), image->height()) |
| : gfx::RectF(gfx::SizeF(quad->resource_size_in_pixels())); |
| |
| if (rpdq_params) { |
| SkPaint paint = params->paint(GetContentColorFilter()); |
| DrawSingleImage(image, valid_texel_bounds, rpdq_params, &paint, params); |
| } else { |
| AddQuadToBatch(image, valid_texel_bounds, params); |
| } |
| } |
| |
| void SkiaRenderer::DrawTextureQuad(const TextureDrawQuad* quad, |
| const DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) { |
| const gfx::ColorSpace& src_color_space = |
| resource_provider_->GetColorSpace(quad->resource_id()); |
| const bool needs_color_conversion_filter = |
| quad->is_video_frame && src_color_space.IsHDR(); |
| |
| ScopedSkImageBuilder builder( |
| this, quad->resource_id(), /*maybe_concurrent_reads=*/true, |
| quad->premultiplied_alpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType, |
| quad->y_flipped ? kBottomLeft_GrSurfaceOrigin : kTopLeft_GrSurfaceOrigin, |
| /*use_target_color_space=*/needs_color_conversion_filter); |
| const SkImage* image = builder.sk_image(); |
| if (!image) |
| return; |
| gfx::RectF uv_rect = gfx::ScaleRect( |
| gfx::BoundingRect(quad->uv_top_left, quad->uv_bottom_right), |
| image->width(), image->height()); |
| params->vis_tex_coords = cc::MathUtil::ScaleRectProportional( |
| uv_rect, gfx::RectF(quad->rect), params->visible_rect); |
| |
| // Use provided resource size if not empty, otherwise use the full image size |
| // as the content area |
| gfx::RectF valid_texel_bounds = |
| quad->resource_size_in_pixels().IsEmpty() |
| ? gfx::RectF(image->width(), image->height()) |
| : gfx::RectF(gfx::SizeF(quad->resource_size_in_pixels())); |
| |
| // There are three scenarios where a texture quad cannot be put into a batch: |
| // 1. It needs to be blended with a constant background color. |
| // 2. The vertex opacities are not all 1s. |
| // 3. The quad contains video which might need special white level adjustment. |
| const bool blend_background = |
| quad->background_color != SK_ColorTRANSPARENT && !image->isOpaque(); |
| const bool vertex_alpha = |
| quad->vertex_opacity[0] < 1.f || quad->vertex_opacity[1] < 1.f || |
| quad->vertex_opacity[2] < 1.f || quad->vertex_opacity[3] < 1.f; |
| |
| if (!blend_background && !vertex_alpha && !needs_color_conversion_filter && |
| !rpdq_params) { |
| // This is a simple texture draw and can go into the batching system |
| DCHECK(!MustFlushBatchedQuads(quad, rpdq_params, *params)); |
| AddQuadToBatch(image, valid_texel_bounds, params); |
| return; |
| } |
| // This needs a color filter for background blending and/or a mask filter |
| // to simulate the vertex opacity, which requires configuring a full SkPaint |
| // and is incompatible with anything batched, but since MustFlushBatchedQuads |
| // was optimistic for TextureQuad's, we're responsible for flushing now. |
| if (!batched_quads_.empty()) |
| FlushBatchedQuads(); |
| |
| SkPaint paint = params->paint(GetContentColorFilter()); |
| |
| float quad_alpha; |
| if (rpdq_params) { |
| // The added color filters for background blending will not apply the |
| // layer's opacity, but make sure there's no vertex_alpha since |
| // CanPassBeDrawnDirectly() should have caught that case. |
| DCHECK(!vertex_alpha); |
| quad_alpha = 1.f; |
| } else { |
| // We will entirely handle the quad's opacity with the mask or color filter |
| quad_alpha = params->opacity; |
| params->opacity = 1.f; |
| } |
| |
| if (vertex_alpha) { |
| // If they are all the same value, combine it with the overall opacity, |
| // otherwise use a mask filter to emulate vertex opacity interpolation |
| if (quad->vertex_opacity[0] == quad->vertex_opacity[1] && |
| quad->vertex_opacity[0] == quad->vertex_opacity[2] && |
| quad->vertex_opacity[0] == quad->vertex_opacity[3]) { |
| quad_alpha *= quad->vertex_opacity[0]; |
| } else { |
| // The only occurrences of non-constant vertex opacities come from unit |
| // tests and src/chrome/browser/android/compositor/decoration_title.cc, |
| // but they always produce the effect of a linear alpha gradient. |
| // All signs indicate point order is [BL, TL, TR, BR] |
| SkPoint gradient_pts[2]; |
| if (quad->vertex_opacity[0] == quad->vertex_opacity[1] && |
| quad->vertex_opacity[2] == quad->vertex_opacity[3]) { |
| // Left to right gradient |
| float y = |
| params->visible_rect.y() + 0.5f * params->visible_rect.height(); |
| gradient_pts[0] = {params->visible_rect.x(), y}; |
| gradient_pts[1] = {params->visible_rect.right(), y}; |
| } else if (quad->vertex_opacity[0] == quad->vertex_opacity[3] && |
| quad->vertex_opacity[1] == quad->vertex_opacity[2]) { |
| // Top to bottom gradient |
| float x = |
| params->visible_rect.x() + 0.5f * params->visible_rect.width(); |
| gradient_pts[0] = {x, params->visible_rect.y()}; |
| gradient_pts[1] = {x, params->visible_rect.bottom()}; |
| } else { |
| // Not sure how to emulate |
| NOTIMPLEMENTED(); |
| return; |
| } |
| |
| float a1 = quad->vertex_opacity[0] * quad_alpha; |
| float a2 = quad->vertex_opacity[2] * quad_alpha; |
| SkColor gradient_colors[2] = {SkColor4f({a1, a1, a1, a1}).toSkColor(), |
| SkColor4f({a2, a2, a2, a2}).toSkColor()}; |
| sk_sp<SkShader> gradient = SkGradientShader::MakeLinear( |
| gradient_pts, gradient_colors, nullptr, 2, SkTileMode::kClamp); |
| paint.setMaskFilter(SkShaderMaskFilter::Make(std::move(gradient))); |
| // shared quad opacity was folded into the gradient, so this will shorten |
| // any color filter chain needed for background blending |
| quad_alpha = 1.f; |
| } |
| } |
| |
| // From gl_renderer, the final src color will be |
| // vertexAlpha * (textureColor + backgroundColor * (1 - textureAlpha)), where |
| // vertexAlpha is the quad's alpha * interpolated per-vertex alpha |
| if (blend_background) { |
| // Add a color filter that does DstOver blending between texture and the |
| // background color. Then, modulate by quad's opacity *after* blending. |
| sk_sp<SkColorFilter> cf = |
| SkColorFilters::Blend(quad->background_color, SkBlendMode::kDstOver); |
| if (quad_alpha < 1.f) { |
| cf = MakeOpacityFilter(quad_alpha, std::move(cf)); |
| quad_alpha = 1.f; |
| DCHECK(cf); |
| } |
| paint.setColorFilter(std::move(cf)); |
| } |
| |
| if (needs_color_conversion_filter) { |
| // Skia won't perform color conversion. |
| const gfx::ColorSpace dst_color_space = CurrentRenderPassColorSpace(); |
| DCHECK(SkColorSpace::Equals(image->colorSpace(), |
| dst_color_space.ToSkColorSpace().get())); |
| sk_sp<SkColorFilter> color_filter = |
| GetColorSpaceConversionFilter(src_color_space, dst_color_space); |
| paint.setColorFilter(color_filter->makeComposed(paint.refColorFilter())); |
| } |
| |
| if (!rpdq_params) { |
| // Reset the paint's alpha, since it started as params.opacity and that |
| // is now applied outside of the paint's alpha. |
| paint.setAlphaf(quad_alpha); |
| } |
| |
| DrawSingleImage(image, valid_texel_bounds, rpdq_params, &paint, params); |
| } |
| |
| void SkiaRenderer::DrawTileDrawQuad(const TileDrawQuad* quad, |
| const DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) { |
| DCHECK(!MustFlushBatchedQuads(quad, rpdq_params, *params)); |
| // |resource_provider_| can be NULL in resourceless software draws, which |
| // should never produce tile quads in the first place. |
| DCHECK(resource_provider_); |
| ScopedSkImageBuilder builder( |
| this, quad->resource_id(), /*maybe_concurrent_reads=*/false, |
| quad->is_premultiplied ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); |
| const SkImage* image = builder.sk_image(); |
| if (!image) |
| return; |
| |
| params->vis_tex_coords = cc::MathUtil::ScaleRectProportional( |
| quad->tex_coord_rect, gfx::RectF(quad->rect), params->visible_rect); |
| // When a tile is at the right or bottom edge of the entire tiled area, its |
| // images won't be fully filled so use the unclipped texture coords. On |
| // interior tiles or left/top tiles, the image has been filled with |
| // overlapping content so the entire image is valid for sampling. |
| gfx::RectF valid_texel_bounds(gfx::SizeF(quad->texture_size)); |
| if (quad->IsRightEdge()) { |
| // Restrict the width to match far side of texture coords |
| valid_texel_bounds.set_width(quad->tex_coord_rect.right()); |
| } |
| if (quad->IsBottomEdge()) { |
| // Restrict the height to match far side of texture coords |
| valid_texel_bounds.set_height(quad->tex_coord_rect.bottom()); |
| } |
| |
| if (rpdq_params) { |
| SkPaint paint = params->paint(GetContentColorFilter()); |
| DrawSingleImage(image, valid_texel_bounds, rpdq_params, &paint, params); |
| } else { |
| AddQuadToBatch(image, valid_texel_bounds, params); |
| } |
| } |
| |
| void SkiaRenderer::DrawYUVVideoQuad(const YUVVideoDrawQuad* quad, |
| const DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) { |
| // Since YUV quads always use a color filter, they require a complex skPaint |
| // that precludes batching. If this changes, we could add YUV quads that don't |
| // require a filter to the batch instead of drawing one at a time. |
| DCHECK(batched_quads_.empty()); |
| |
| gfx::ColorSpace src_color_space = quad->video_color_space; |
| // Invalid or unspecified color spaces should be treated as REC709. |
| if (!src_color_space.IsValid()) |
| src_color_space = gfx::ColorSpace::CreateREC709(); |
| |
| // We might modify |dst_color_space| to be something other than the |
| // destination color space for the frame. The generated SkColorFilter does the |
| // real color space adjustment. To avoid having skia also try to adjust the |
| // color space we lie and say the SkImage destination color space is always |
| // the same as the rest of the frame. Otherwise the two color space |
| // adjustments combined will produce the wrong result. |
| const gfx::ColorSpace& frame_color_space = CurrentRenderPassColorSpace(); |
| gfx::ColorSpace dst_color_space = frame_color_space; |
| |
| #if defined(OS_WIN) |
| // Force sRGB output on Windows for overlay candidate video quads to match |
| // DirectComposition behavior in case these switch between overlays and |
| // compositing. See https://crbug.com/811118 for details. |
| // Currently if HDR is supported, OverlayProcessor doesn't promote HDR video |
| // frame as overlay candidate. So it's unnecessary to worry about the |
| // compositing-overlay switch here. In addition drawing a HDR video using sRGB |
| // can cancel the advantages of HDR. |
| const bool supports_dc_layers = |
| output_surface_->capabilities().supports_dc_layers; |
| if (supports_dc_layers && !src_color_space.IsHDR() && |
| resource_provider_->IsOverlayCandidate(quad->y_plane_resource_id())) { |
| DCHECK(resource_provider_->IsOverlayCandidate(quad->u_plane_resource_id())); |
| dst_color_space = gfx::ColorSpace::CreateSRGB(); |
| } |
| #endif |
| |
| DCHECK(resource_provider_); |
| // Pass in |frame_color_space| here instead of |dst_color_space| so the color |
| // space transform going from SkImage to SkSurface is identity. The |
| // SkColorFilter already handles color space conversion so this avoids |
| // applying the conversion twice. |
| ScopedYUVSkImageBuilder builder(this, quad, |
| frame_color_space.ToSkColorSpace()); |
| const SkImage* image = builder.sk_image(); |
| if (!image) |
| return; |
| |
| params->vis_tex_coords = cc::MathUtil::ScaleRectProportional( |
| quad->ya_tex_coord_rect, gfx::RectF(quad->rect), params->visible_rect); |
| |
| // Use provided, unclipped texture coordinates as the content area, which will |
| // force coord clamping unless the geometry was clipped, or they span the |
| // entire YUV image. |
| SkPaint paint = params->paint(GetContentColorFilter()); |
| |
| sk_sp<SkColorFilter> color_filter = GetColorSpaceConversionFilter( |
| src_color_space, dst_color_space, quad->resource_offset, |
| quad->resource_multiplier); |
| paint.setColorFilter(color_filter->makeComposed(paint.refColorFilter())); |
| |
| DrawSingleImage(image, quad->ya_tex_coord_rect, rpdq_params, &paint, params); |
| } |
| |
| void SkiaRenderer::DrawUnsupportedQuad(const DrawQuad* quad, |
| const DrawRPDQParams* rpdq_params, |
| DrawQuadParams* params) { |
| #ifdef NDEBUG |
| DrawColoredQuad(SK_ColorWHITE, rpdq_params, params); |
| #else |
| DrawColoredQuad(SK_ColorMAGENTA, rpdq_params, params); |
| #endif |
| } |
| |
| void SkiaRenderer::ScheduleOverlays() { |
| DCHECK(!current_frame_resource_fence_->WasSet()); |
| |
| pending_overlay_locks_.emplace_back(); |
| if (current_frame()->overlay_list.empty()) |
| return; |
| |
| auto& locks = pending_overlay_locks_.back(); |
| std::vector<gpu::SyncToken> sync_tokens; |
| |
| #if !defined(OS_WIN) |
| DCHECK(output_surface_->capabilities().supports_surfaceless); |
| #endif |
| |
| #if defined(OS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH) |
| // CrOS and Android SurfaceControl use this code path. Android classic has |
| // switched over to OverlayProcessor. |
| // TODO(weiliangc): Remove this when CrOS and Android SurfaceControl switch |
| // to OverlayProcessor as well. |
| for (auto& overlay : current_frame()->overlay_list) { |
| // Resources will be unlocked after the next SwapBuffers() is completed. |
| locks.emplace_back(resource_provider_, overlay.resource_id); |
| auto& lock = locks.back(); |
| |
| // Sync tokens ensure the texture to be overlaid is available before |
| // scheduling it for display. |
| if (lock.sync_token().HasData()) |
| sync_tokens.push_back(lock.sync_token()); |
| |
| overlay.mailbox = lock.mailbox(); |
| DCHECK(!overlay.mailbox.IsZero()); |
| } |
| #elif defined(OS_WIN) |
| for (auto& dc_layer_overlay : current_frame()->overlay_list) { |
| for (size_t i = 0; i < DCLayerOverlay::kNumResources; ++i) { |
| ResourceId resource_id = dc_layer_overlay.resources[i]; |
| if (resource_id == kInvalidResourceId) |
| break; |
| |
| // Resources will be unlocked after the next SwapBuffers() is completed. |
| locks.emplace_back(resource_provider_, resource_id); |
| auto& lock = locks.back(); |
| |
| // Sync tokens ensure the texture to be overlaid is available before |
| // scheduling it for display. |
| if (lock.sync_token().HasData()) |
| sync_tokens.push_back(lock.sync_token()); |
| |
| dc_layer_overlay.mailbox[i] = lock.mailbox(); |
| } |
| DCHECK(!dc_layer_overlay.mailbox[0].IsZero()); |
| } |
| #elif defined(OS_APPLE) |
| for (CALayerOverlay& ca_layer_overlay : current_frame()->overlay_list) { |
| if (ca_layer_overlay.rpdq) { |
| PrepareRenderPassOverlay(&ca_layer_overlay); |
| continue; |
| } |
| // Some overlays are for solid-color layers. |
| if (!ca_layer_overlay.contents_resource_id) |
| continue; |
| |
| // TODO(https://crbug.com/894929): Track IOSurface in-use instead of just |
| // unlocking after the next SwapBuffers is completed. |
| locks.emplace_back(resource_provider_, |
| ca_layer_overlay.contents_resource_id); |
| auto& lock = locks.back(); |
| |
| // Sync tokens ensure the texture to be overlaid is available before |
| // scheduling it for display. |
| if (lock.sync_token().HasData()) |
| sync_tokens.push_back(lock.sync_token()); |
| |
| // Populate the |mailbox| of the CALayerOverlay which will be used to look |
| // up the corresponding GLImageIOSurface when building the CALayer tree. |
| ca_layer_overlay.mailbox = lock.mailbox(); |
| DCHECK(!ca_layer_overlay.mailbox.IsZero()); |
| } |
| #elif defined(USE_OZONE) |
| // For platforms that don't support overlays, the |
| // current_frame()->overlay_list should be empty, and this code should not be |
| // reached. |
| if (!features::IsUsingOzonePlatform()) { |
| NOTREACHED(); |
| return; |
| } |
| // Only Wayland uses this code path. |
| for (auto& overlay : current_frame()->overlay_list) { |
| // Resources will be unlocked after the next SwapBuffers() is completed. |
| locks.emplace_back(resource_provider_, overlay.resource_id); |
| auto& lock = locks.back(); |
| |
| // Sync tokens ensure the texture to be overlaid is available before |
| // scheduling it for display. |
| if (lock.sync_token().HasData()) |
| sync_tokens.push_back(lock.sync_token()); |
| |
| overlay.mailbox = lock.mailbox(); |
| DCHECK(!overlay.mailbox.IsZero()); |
| } |
| #else // defined(OS_ANDROID) |
| // For platforms that don't support overlays, the |
| // current_frame()->overlay_list should be empty, and this code should not be |
| // reached. |
| NOTREACHED(); |
| #endif // defined(OS_ANDROID) |
| |
| base::OnceClosure on_finished_callback; |
| if (current_frame_resource_fence_->WasSet()) { |
| on_finished_callback = base::BindOnce( |
| &FrameResourceFence::Signal, std::move(current_frame_resource_fence_)); |
| current_frame_resource_fence_ = base::MakeRefCounted<FrameResourceFence>(); |
| resource_provider_->SetReadLockFence(current_frame_resource_fence_.get()); |
| } |
| |
| skia_output_surface_->ScheduleOverlays( |
| std::move(current_frame()->overlay_list), std::move(sync_tokens), |
| std::move(on_finished_callback)); |
| } |
| |
| sk_sp<SkColorFilter> SkiaRenderer::GetColorSpaceConversionFilter( |
| const gfx::ColorSpace& src, |
| const gfx::ColorSpace& dst, |
| float resource_offset, |
| float resource_multiplier) { |
| // If the input color space is HDR, and it did not specify a white level, |
| // override it with the frame's white level. |
| gfx::ColorSpace adjusted_src = src.GetWithSDRWhiteLevel( |
| current_frame()->display_color_spaces.GetSDRWhiteLevel()); |
| |
| sk_sp<SkRuntimeEffect>& effect = color_filter_cache_[dst][adjusted_src]; |
| if (!effect) { |
| std::unique_ptr<gfx::ColorTransform> transform = |
| gfx::ColorTransform::NewColorTransform( |
| adjusted_src, dst, gfx::ColorTransform::Intent::INTENT_PERCEPTUAL); |
| |
| const char* hdr = R"( |
| uniform half offset; |
| uniform half multiplier; |
| in shader child; |
| |
| half4 main() { |
| half4 color = sample(child); |
| // un-premultiply alpha |
| if (color.a > 0) |
| color.rgb /= color.a; |
| |
| color.rgb -= offset; |
| color.rgb *= multiplier; |
| )"; |
| const char* ftr = R"( |
| // premultiply alpha |
| color.rgb *= color.a; |
| return color; |
| } |
| )"; |
| |
| std::string shader = hdr + transform->GetSkShaderSource() + ftr; |
| |
| effect = std::get<0>( |
| SkRuntimeEffect::Make(SkString(shader.c_str(), shader.size()))); |
| DCHECK(effect); |
| } |
| |
| YUVInput input; |
| input.offset = resource_offset; |
| input.multiplier = resource_multiplier; |
| sk_sp<SkData> data = SkData::MakeWithCopy(&input, sizeof(input)); |
| |
| sk_sp<SkColorFilter> child = nullptr; // = default input color |
| return effect->makeColorFilter(std::move(data), &child, 1); |
| } |
| |
| sk_sp<SkColorFilter> SkiaRenderer::GetContentColorFilter() { |
| sk_sp<SkColorFilter> color_transform = nullptr; |
| if (current_canvas_ == root_canvas_ && |
| !output_surface_->color_matrix().isIdentity()) { |
| std::array<float, 20> values; |
| values.fill(0.0f); |
| for (uint32_t r = 0; r < 4; r++) { |
| for (uint32_t c = 0; c < 4; c++) { |
| values[r * 5 + c] = output_surface_->color_matrix().getFloat(r, c); |
| } |
| } |
| color_transform = SkColorFilters::Matrix(values.data()); |
| } |
| |
| sk_sp<SkColorFilter> tint_transform = nullptr; |
| if (current_canvas_ == root_canvas_ && |
| debug_settings_->tint_composited_content) { |
| std::array<float, 20> values; |
| values.fill(0.0f); |
| auto tint = cc::DebugColors::TintCompositedContentColorTransformMatrix(); |
| for (int r = 0; r < 4; r++) { |
| for (int c = 0; c < 4; c++) { |
| values[r * 5 + c] = tint[c * 4 + r]; |
| } |
| } |
| tint_transform = SkColorFilters::Matrix(values.data()); |
| } |
| |
| if (color_transform) { |
| return tint_transform ? color_transform->makeComposed(tint_transform) |
| : color_transform; |
| } else { |
| return tint_transform; |
| } |
| } |
| |
| SkiaRenderer::DrawRPDQParams SkiaRenderer::CalculateRPDQParams( |
| const AggregatedRenderPassDrawQuad* quad, |
| DrawQuadParams* params) { |
| DrawRPDQParams rpdq_params(params->visible_rect); |
| |
| // Prepare mask. |
| ResourceId mask_resource_id = quad->mask_resource_id(); |
| ScopedSkImageBuilder mask_image_builder(this, mask_resource_id, |
| /*maybe_concurrent_reads=*/false); |
| const SkImage* mask_image = mask_image_builder.sk_image(); |
| DCHECK_EQ(!!mask_resource_id, !!mask_image); |
| if (mask_image) { |
| // Scale normalized uv rect into absolute texel coordinates. |
| SkRect mask_rect = gfx::RectFToSkRect( |
| gfx::ScaleRect(quad->mask_uv_rect, quad->mask_texture_size.width(), |
| quad->mask_texture_size.height())); |
| // Map to full quad rect so that mask coordinates don't change with clipping |
| SkMatrix mask_to_quad_matrix = SkMatrix::MakeRectToRect( |
| mask_rect, gfx::RectToSkRect(quad->rect), SkMatrix::kFill_ScaleToFit); |
| |
| rpdq_params.mask_shader = mask_image->makeShader( |
| SkTileMode::kClamp, SkTileMode::kClamp, |
| SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone), |
| &mask_to_quad_matrix); |
| } |
| |
| const cc::FilterOperations* filters = FiltersForPass(quad->render_pass_id); |
| const cc::FilterOperations* backdrop_filters = |
| BackdropFiltersForPass(quad->render_pass_id); |
| // Early out if there are no filters to convert to SkImageFilters |
| if (!filters && !backdrop_filters) { |
| return rpdq_params; |
| } |
| |
| // Calculate local matrix that's shared by filters and backdrop_filters |
| SkMatrix local_matrix; |
| local_matrix.setTranslate(quad->filters_origin.x(), quad->filters_origin.y()); |
| local_matrix.postScale(quad->filters_scale.x(), quad->filters_scale.y()); |
| |
| gfx::SizeF filter_size(quad->rect.width(), quad->rect.height()); |
| |
| // Convert CC image filters into a SkImageFilter root node |
| if (filters) { |
| DCHECK(!filters->IsEmpty()); |
| auto paint_filter = |
| cc::RenderSurfaceFilters::BuildImageFilter(*filters, filter_size); |
| auto sk_filter = paint_filter ? paint_filter->cached_sk_filter_ : nullptr; |
| |
| if (sk_filter) { |
| // Update the filter bounds based to account for how the image filters |
| // grow or expand the area touched by drawing. |
| rpdq_params.filter_bounds = |
| filters->MapRect(rpdq_params.filter_bounds, local_matrix); |
| |
| // If after applying the filter we would be clipped out, skip the draw. |
| gfx::Rect clip_rect = quad->shared_quad_state->clip_rect; |
| if (clip_rect.IsEmpty()) { |
| clip_rect = current_draw_rect_; |
| } |
| const gfx::Transform& transform = |
| quad->shared_quad_state->quad_to_target_transform; |
| gfx::QuadF clip_quad = gfx::QuadF(gfx::RectF(clip_rect)); |
| gfx::QuadF local_clip = |
| cc::MathUtil::InverseMapQuadToLocalSpace(transform, clip_quad); |
| |
| rpdq_params.filter_bounds.Intersect( |
| gfx::ToEnclosingRect(local_clip.BoundingBox())); |
| // If we've been fully clipped out (by crop rect or clipping), there's |
| // nothing to draw. |
| if (rpdq_params.filter_bounds.IsEmpty()) { |
| return rpdq_params; |
| } |
| |
| rpdq_params.image_filter = sk_filter->makeWithLocalMatrix(local_matrix); |
| |
| // Attempt to simplify the image filter to a color filter, which enables |
| // the RPDQ effects to be applied more efficiently. |
| SkColorFilter* color_filter_ptr = nullptr; |
| if (rpdq_params.image_filter) { |
| if (rpdq_params.image_filter->asAColorFilter(&color_filter_ptr)) { |
| // asAColorFilter already ref'ed the filter when true is returned, |
| // reset() does not add a ref itself, so everything is okay. |
| rpdq_params.color_filter.reset(color_filter_ptr); |
| } |
| } |
| } |
| } |
| |
| // Convert CC image filters for the backdrop into a SkImageFilter root node |
| // TODO(weiliangc): ChromeOS would need backdrop_filter_quality implemented |
| if (backdrop_filters) { |
| DCHECK(!backdrop_filters->IsEmpty()); |
| |
| // Must account for clipping that occurs for backdrop filters, since their |
| // input content has already been clipped to the output rect. |
| gfx::Rect device_rect = gfx::ToEnclosingRect(cc::MathUtil::MapClippedRect( |
| params->content_device_transform, gfx::RectF(quad->rect))); |
| gfx::Rect out_rect = MoveFromDrawToWindowSpace( |
| current_frame()->current_render_pass->output_rect); |
| out_rect.Intersect(device_rect); |
| gfx::Vector2dF offset = |
| (device_rect.top_right() - out_rect.top_right()) + |
| (device_rect.bottom_left() - out_rect.bottom_left()); |
| |
| auto bg_paint_filter = cc::RenderSurfaceFilters::BuildImageFilter( |
| *backdrop_filters, gfx::SizeF(out_rect.size()), offset); |
| auto sk_bg_filter = |
| bg_paint_filter ? bg_paint_filter->cached_sk_filter_ : nullptr; |
| |
| if (sk_bg_filter) { |
| rpdq_params.backdrop_filter = |
| sk_bg_filter->makeWithLocalMatrix(local_matrix); |
| } |
| } |
| |
| // Determine if the backdrop filter has its own clip (which only needs to be |
| // checked when we have a backdrop filter to apply) |
| if (rpdq_params.backdrop_filter) { |
| const base::Optional<gfx::RRectF> backdrop_filter_bounds = |
| BackdropFilterBoundsForPass(quad->render_pass_id); |
| if (backdrop_filter_bounds) { |
| // The backdrop filters effect will be cropped by these bounds. If the |
| // bounds are empty, discard the backdrop filter now since none of it |
| // would have been visible anyways. |
| if (backdrop_filter_bounds->IsEmpty()) { |
| rpdq_params.backdrop_filter = nullptr; |
| } else { |
| rpdq_params.backdrop_filter_bounds = *backdrop_filter_bounds; |
| // Scale by the filter's scale, but don't apply filter origin |
| rpdq_params.backdrop_filter_bounds->Scale(quad->filters_scale.x(), |
| quad->filters_scale.y()); |
| |
| // If there are also regular image filters, they apply to the area of |
| // the backdrop_filter_bounds too, so expand the backdrop bounds and |
| // join it with the main filter bounds. |
| if (rpdq_params.image_filter) { |
| gfx::Rect backdrop_rect = |
| gfx::ToEnclosingRect(rpdq_params.backdrop_filter_bounds->rect()); |
| rpdq_params.filter_bounds.Union( |
| filters->MapRect(backdrop_rect, local_matrix)); |
| } |
| } |
| } |
| } |
| |
| return rpdq_params; |
| } |
| |
| void SkiaRenderer::DrawRenderPassQuad(const AggregatedRenderPassDrawQuad* quad, |
| DrawQuadParams* params) { |
| DrawRPDQParams rpdq_params = CalculateRPDQParams(quad, params); |
| |
| // |filter_bounds| is the content space bounds that includes any filtered |
| // extents. If empty, the draw can be skipped. |
| if (rpdq_params.filter_bounds.IsEmpty()) |
| return; |
| |
| auto bypass = render_pass_bypass_quads_.find(quad->render_pass_id); |
| // When Render Pass has a single quad inside we would draw that directly. |
| if (bypass != render_pass_bypass_quads_.end()) { |
| BypassMode mode = |
| CalculateBypassParams(bypass->second, &rpdq_params, params); |
| if (mode == BypassMode::kDrawTransparentQuad) { |
| DrawColoredQuad(SK_ColorTRANSPARENT, &rpdq_params, params); |
| } else if (mode == BypassMode::kDrawBypassQuad) { |
| DrawQuadInternal(bypass->second, &rpdq_params, params); |
| } // else mode == kSkip |
| return; |
| } |
| |
| // A real render pass that was turned into an image |
| auto iter = render_pass_backings_.find(quad->render_pass_id); |
| DCHECK(render_pass_backings_.end() != iter); |
| // This function is called after AllocateRenderPassResourceIfNeeded, so |
| // there should be backing ready. |
| RenderPassBacking& backing = iter->second; |
| |
| sk_sp<SkImage> content_image = |
| skia_output_surface_->MakePromiseSkImageFromRenderPass( |
| quad->render_pass_id, backing.size, backing.format, |
| backing.generate_mipmap, backing.color_space.ToSkColorSpace()); |
| DLOG_IF(ERROR, !content_image) |
| << "MakePromiseSkImageFromRenderPass() failed for render pass"; |
| |
| if (!content_image) |
| return; |
| |
| // If the RP generated mipmaps when it was created, set quality to medium, |
| // which turns on mipmap filtering in Skia. |
| if (backing.generate_mipmap) |
| params->filter_quality = kMedium_SkFilterQuality; |
| |
| params->vis_tex_coords = cc::MathUtil::ScaleRectProportional( |
| quad->tex_coord_rect, gfx::RectF(quad->rect), params->visible_rect); |
| gfx::RectF valid_texel_bounds(content_image->width(), |
| content_image->height()); |
| |
| // When the RPDQ was needed because of a copy request, it may not require any |
| // advanced filtering/effects at which point it's basically a tiled quad. |
| if (!rpdq_params.image_filter && !rpdq_params.backdrop_filter && |
| !rpdq_params.mask_shader) { |
| DCHECK(!MustFlushBatchedQuads(quad, nullptr, *params)); |
| AddQuadToBatch(content_image.get(), valid_texel_bounds, params); |
| return; |
| } |
| |
| // The paint is complex enough that it has to be drawn on its own, and since |
| // MustFlushBatchedQuads() was optimistic, we manage the flush here. |
| if (!batched_quads_.empty()) |
| FlushBatchedQuads(); |
| |
| SkPaint paint = params->paint(GetContentColorFilter()); |
| |
| DrawSingleImage(content_image.get(), valid_texel_bounds, &rpdq_params, &paint, |
| params); |
| } |
| |
| void SkiaRenderer::CopyDrawnRenderPass( |
| const copy_output::RenderPassGeometry& geometry, |
| std::unique_ptr<CopyOutputRequest> request) { |
| // TODO(weiliangc): Make copy request work. (crbug.com/644851) |
| TRACE_EVENT0("viz", "SkiaRenderer::CopyDrawnRenderPass"); |
| |
| // Root framebuffer uses id 0 in SkiaOutputSurface. |
| AggregatedRenderPassId render_pass_id; |
| const auto* const render_pass = current_frame()->current_render_pass; |
| if (render_pass != current_frame()->root_render_pass) { |
| render_pass_id = render_pass->id; |
| } |
| skia_output_surface_->CopyOutput(render_pass_id, geometry, |
| CurrentRenderPassColorSpace(), |
| std::move(request)); |
| } |
| |
| void SkiaRenderer::DidChangeVisibility() { |
| if (visible_) |
| output_surface_->EnsureBackbuffer(); |
| else |
| output_surface_->DiscardBackbuffer(); |
| } |
| |
| void SkiaRenderer::FinishDrawingQuadList() { |
| if (!batched_quads_.empty()) |
| FlushBatchedQuads(); |
| |
| bool is_root_render_pass = |
| current_frame()->current_render_pass == current_frame()->root_render_pass; |
| |
| // Drawing the delegated ink trail must happen after the final |
| // FlushBatchedQuads() call so that the trail can always be on top of |
| // everything else that has already been drawn on the page. For the same |
| // reason, it should only happen on the root render pass. |
| if (is_root_render_pass && delegated_ink_point_renderer_) |
| DrawDelegatedInkTrail(); |
| |
| base::OnceClosure on_finished_callback; |
| // Signal |current_frame_resource_fence_| when the root render pass is |
| // finished. |
| if (current_frame_resource_fence_->WasSet()) { |
| on_finished_callback = base::BindOnce( |
| &FrameResourceFence::Signal, std::move(current_frame_resource_fence_)); |
| current_frame_resource_fence_ = base::MakeRefCounted<FrameResourceFence>(); |
| resource_provider_->SetReadLockFence(current_frame_resource_fence_.get()); |
| } |
| skia_output_surface_->EndPaint(std::move(on_finished_callback)); |
| |
| // Defer flushing drawing task for root render pass, to avoid extra |
| // MakeCurrent() call. It is expensive on GL. |
| // TODO(https://crbug.com/1141008): Consider deferring drawing tasks for |
| // all render passes. |
| if (is_root_render_pass) |
| return; |
| |
| FlushOutputSurface(); |
| } |
| |
| void SkiaRenderer::GenerateMipmap() { |
| // This is a no-op since setting FilterQuality to high during drawing of |
| // CompositorRenderPassDrawQuad is what actually generates generate_mipmap. |
| } |
| |
| void SkiaRenderer::UpdateRenderPassTextures( |
| const AggregatedRenderPassList& render_passes_in_draw_order, |
| const base::flat_map<AggregatedRenderPassId, RenderPassRequirements>& |
| render_passes_in_frame) { |
| std::vector<AggregatedRenderPassId> passes_to_delete; |
| for (const auto& pair : render_pass_backings_) { |
| auto render_pass_it = render_passes_in_frame.find(pair.first); |
| if (render_pass_it == render_passes_in_frame.end()) { |
| passes_to_delete.push_back(pair.first); |
| continue; |
| } |
| |
| const RenderPassRequirements& requirements = render_pass_it->second; |
| const RenderPassBacking& backing = pair.second; |
| bool size_appropriate = backing.size.width() >= requirements.size.width() && |
| backing.size.height() >= requirements.size.height(); |
| bool mipmap_appropriate = |
| !requirements.generate_mipmap || backing.generate_mipmap; |
| if (!size_appropriate || !mipmap_appropriate) |
| passes_to_delete.push_back(pair.first); |
| } |
| |
| // Delete RenderPass backings from the previous frame that will not be used |
| // again. |
| for (size_t i = 0; i < passes_to_delete.size(); ++i) { |
| auto it = render_pass_backings_.find(passes_to_delete[i]); |
| render_pass_backings_.erase(it); |
| } |
| |
| if (!passes_to_delete.empty()) { |
| skia_output_surface_->RemoveRenderPassResource(std::move(passes_to_delete)); |
| } |
| } |
| |
| void SkiaRenderer::AllocateRenderPassResourceIfNeeded( |
| const AggregatedRenderPassId& render_pass_id, |
| const RenderPassRequirements& requirements) { |
| auto it = render_pass_backings_.find(render_pass_id); |
| if (it != render_pass_backings_.end()) { |
| DCHECK(gfx::Rect(it->second.size).Contains(gfx::Rect(requirements.size))); |
| return; |
| } |
| |
| auto color_space = CurrentRenderPassColorSpace(); |
| // TODO(penghuang): check supported format correctly. |
| gpu::Capabilities caps; |
| caps.texture_format_bgra8888 = true; |
| auto format = color_space.IsHDR() |
| ? RGBA_F16 |
| : PlatformColor::BestSupportedTextureFormat(caps); |
| render_pass_backings_.emplace( |
| render_pass_id, |
| RenderPassBacking({requirements.size, requirements.generate_mipmap, |
| color_space, format})); |
| } |
| |
| void SkiaRenderer::FlushOutputSurface() { |
| auto sync_token = skia_output_surface_->Flush(); |
| lock_set_for_external_use_->UnlockResources(sync_token); |
| } |
| |
| #if defined(OS_APPLE) |
| void SkiaRenderer::PrepareRenderPassOverlay(CALayerOverlay* overlay) { |
| DCHECK(!current_canvas_); |
| DCHECK(batched_quads_.empty()); |
| DCHECK(overlay->rpdq); |
| |
| auto* const quad = overlay->rpdq; |
| |
| // The overlay will be sent to GPU the thread, so set rpdq to nullptr to avoid |
| // being accessed on the GPU thread. |
| overlay->rpdq = nullptr; |
| |
| // The |current_render_pass| could be used for calculating destination |
| // color space or clipping rect for backdrop filters. However |
| // the |current_render_pass| is nullptr during ScheduleOverlays(), since all |
| // overlay quads should be in the |root_render_pass|, before they are promoted |
| // to overlays, so set the |root_render_pass| to the |current_render_pass|. |
| base::AutoReset<const AggregatedRenderPass*> auto_reset_current_render_pass( |
| ¤t_frame()->current_render_pass, current_frame()->root_render_pass); |
| |
| auto* shared_quad_state = |
| const_cast<SharedQuadState*>(quad->shared_quad_state); |
| |
| gfx::Transform quad_to_target_transform_inverse( |
| gfx::Transform::kSkipInitialization); |
| if (shared_quad_state->is_clipped || |
| !shared_quad_state->mask_filter_info.IsEmpty()) { |
| bool result = shared_quad_state->quad_to_target_transform.GetInverse( |
| &quad_to_target_transform_inverse); |
| DCHECK(result) << "quad_to_target_transform.GetInverse() failed"; |
| } |
| |
| // The |clip_rect| is in the device coordinate and with all transforms |
| // (translation, scaling, rotation, etc), so remove them. |
| base::Optional<base::AutoReset<gfx::Rect>> auto_reset_clip_rect; |
| if (shared_quad_state->is_clipped) { |
| gfx::RectF clip_rect(shared_quad_state->clip_rect); |
| quad_to_target_transform_inverse.TransformRect(&clip_rect); |
| auto_reset_clip_rect.emplace(&shared_quad_state->clip_rect, |
| gfx::ToEnclosedRect(clip_rect)); |
| } |
| |
| // The |mask_filter_info| is in the device coordinate and with all transforms |
| // (translation, scaling, rotation, etc), so remove them. |
| if (!shared_quad_state->mask_filter_info.IsEmpty()) { |
| auto result = shared_quad_state->mask_filter_info.Transform( |
| quad_to_target_transform_inverse); |
| DCHECK(result) << "shared_quad_state->mask_filter_info.Transform() failed."; |
| } |
| |
| // Reset |quad_to_target_transform|, so the quad will be rendered at the |
| // origin (0,0) without all transforms (translation, scaling, rotation, etc) |
| // and then we will use OS compositor to do those transforms. |
| base::AutoReset<gfx::Transform> auto_reset_transform( |
| &shared_quad_state->quad_to_target_transform, gfx::Transform()); |
| |
| const auto& viewport_size = current_frame()->device_viewport_size; |
| auto projection_matrix = gfx::OrthoProjectionMatrix( |
| /*left=*/0, /*right=*/viewport_size.width(), /*bottom=*/0, |
| /*top=*/viewport_size.height()); |
| auto window_matrix = |
| gfx::WindowMatrix(/*x=*/0, /*y=*/0, /*width=*/viewport_size.width(), |
| /*height=*/viewport_size.height()); |
| |
| gfx::Transform target_to_device = window_matrix * projection_matrix; |
| |
| // Use nullptr scissor, so we can always render the whole render pass in an |
| // overlay backing. |
| // TODO(penghuang): reusing overlay backing from previous frame to avoid |
| // reproducing the overlay backing if the render pass content quad properties |
| // and content are not changed. |
| DrawQuadParams params = CalculateDrawQuadParams( |
| target_to_device, /*scissor=*/nullptr, quad, /*draw_region=*/nullptr); |
| DrawRPDQParams rpdq_params = CalculateRPDQParams(quad, ¶ms); |
| |
| const auto& filter_bounds = rpdq_params.filter_bounds; |
| |
| // |filter_bounds| is the content space bounds that includes any filtered |
| // extents. If empty, the draw can be skipped. |
| if (filter_bounds.IsEmpty()) |
| return; |
| |
| ResourceFormat buffer_format{}; |
| gfx::ColorSpace color_space; |
| |
| RenderPassBacking* backing = nullptr; |
| auto bypass = render_pass_bypass_quads_.find(quad->render_pass_id); |
| BypassMode bypass_mode = BypassMode::kSkip; |
| // When Render Pass has a single quad inside we would draw that directly. |
| if (bypass != render_pass_bypass_quads_.end()) { |
| bypass_mode = CalculateBypassParams(bypass->second, &rpdq_params, ¶ms); |
| if (bypass_mode == BypassMode::kSkip) |
| return; |
| // For bypassed render pass, we use the same format and color space for the |
| // framebuffer. |
| buffer_format = GetResourceFormat(reshape_buffer_format()); |
| color_space = reshape_color_space(); |
| } else { |
| // A real render pass that was turned into an image |
| auto it = render_pass_backings_.find(quad->render_pass_id); |
| DCHECK(render_pass_backings_.end() != it); |
| // This function is called after AllocateRenderPassResourceIfNeeded, so |
| // there should be backing ready. |
| backing = &it->second; |
| buffer_format = backing->format; |
| color_space = backing->color_space; |
| } |
| |
| |
| // Adjust the overlay |buffer_size| to reduce memory fragmentation. It also |
| // increases buffer reusing possibilities. |
| constexpr int kBufferMultiple = 64; |
| gfx::Size buffer_size( |
| cc::MathUtil::CheckedRoundUp(filter_bounds.width(), kBufferMultiple), |
| cc::MathUtil::CheckedRoundUp(filter_bounds.height(), kBufferMultiple)); |
| |
| current_canvas_ = skia_output_surface_->BeginPaintRenderPassOverlay( |
| buffer_size, buffer_format, /*mipmap=*/false, |
| color_space.ToSkColorSpace()); |
| if (!current_canvas_) { |
| DLOG(ERROR) << "BeginPaintRenderPassOverlay() failed."; |
| return; |
| } |
| |
| // Clear the backing to ARGB(0,0,0,0). |
| current_canvas_->clear(SkColorSetARGB(0, 0, 0, 0)); |
| |
| // Adjust the |content_device_transform| to make sure filter extends are drawn |
| // inside of the buffer. |
| params.content_device_transform.Translate(-filter_bounds.x(), |
| -filter_bounds.y()); |
| |
| // Also adjust the |rounded_corner_bounds| to the new location. |
| if (params.rounded_corner_bounds) { |
| params.rounded_corner_bounds->Offset(-filter_bounds.x(), |
| -filter_bounds.y()); |
| } |
| |
| // When Render Pass has a single quad inside we would draw that directly. |
| if (bypass != render_pass_bypass_quads_.end()) { |
| if (bypass_mode == BypassMode::kDrawTransparentQuad) { |
| DrawColoredQuad(SK_ColorTRANSPARENT, &rpdq_params, ¶ms); |
| } else if (bypass_mode == BypassMode::kDrawBypassQuad) { |
| DrawQuadInternal(bypass->second, &rpdq_params, ¶ms); |
| } else { |
| NOTREACHED(); |
| } |
| } else { |
| DCHECK(backing); |
| auto content_image = skia_output_surface_->MakePromiseSkImageFromRenderPass( |
| quad->render_pass_id, backing->size, backing->format, |
| backing->generate_mipmap, backing->color_space.ToSkColorSpace()); |
| if (!content_image) { |
| DLOG(ERROR) |
| << "MakePromiseSkImageFromRenderPass() failed for render pass"; |
| skia_output_surface_->EndPaintRenderPassOverlay(); |
| return; |
| } |
| |
| // If the RP generated mipmaps when it was created, set quality to medium, |
| // which turns on mipmap filtering in Skia. |
| if (backing->generate_mipmap) |
| params.filter_quality = kMedium_SkFilterQuality; |
| |
| params.vis_tex_coords = cc::MathUtil::ScaleRectProportional( |
| quad->tex_coord_rect, gfx::RectF(quad->rect), params.visible_rect); |
| gfx::RectF valid_texel_bounds(content_image->width(), |
| content_image->height()); |
| |
| SkPaint paint = params.paint(GetContentColorFilter()); |
| DrawSingleImage(content_image.get(), valid_texel_bounds, &rpdq_params, |
| &paint, ¶ms); |
| } |
| |
| current_canvas_ = nullptr; |
| auto ddl = skia_output_surface_->EndPaintRenderPassOverlay(); |
| DCHECK(ddl); |
| |
| // Put overlay related information in CALayerOverlay, |
| // so SkiaOutputSurfaceImplOnGpu can use the DDL to create overlay buffer and |
| // play the DDL back to it accordingly. |
| overlay->ddl = std::move(ddl); |
| |
| // Adjust |bounds_rect| to contain the whole buffer and at the right location. |
| overlay->bounds_rect.set_origin(gfx::PointF(filter_bounds.origin())); |
| overlay->bounds_rect.set_size(gfx::SizeF(buffer_size)); |
| } |
| #endif |
| |
| bool SkiaRenderer::IsRenderPassResourceAllocated( |
| const AggregatedRenderPassId& render_pass_id) const { |
| auto it = render_pass_backings_.find(render_pass_id); |
| return it != render_pass_backings_.end(); |
| } |
| |
| gfx::Size SkiaRenderer::GetRenderPassBackingPixelSize( |
| const AggregatedRenderPassId& render_pass_id) { |
| auto it = render_pass_backings_.find(render_pass_id); |
| DCHECK(it != render_pass_backings_.end()); |
| return it->second.size; |
| } |
| |
| bool SkiaRenderer::CreateDelegatedInkPointRenderer() { |
| DCHECK(!delegated_ink_point_renderer_); |
| delegated_ink_point_renderer_ = |
| std::make_unique<DelegatedInkPointRendererSkia>(); |
| return true; |
| } |
| |
| void SkiaRenderer::DrawDelegatedInkTrail() { |
| delegated_ink_point_renderer_->DrawDelegatedInkTrail(current_canvas_); |
| } |
| |
| DelegatedInkPointRendererBase* SkiaRenderer::GetDelegatedInkPointRenderer() { |
| if (!delegated_ink_point_renderer_ && !CreateDelegatedInkPointRenderer()) |
| return nullptr; |
| |
| return delegated_ink_point_renderer_.get(); |
| } |
| |
| #if defined(OS_APPLE) |
| bool SkiaRenderer::ScopedReadLockComparator::operator()( |
| const DisplayResourceProvider::ScopedReadLockSharedImage& lhs, |
| const DisplayResourceProvider::ScopedReadLockSharedImage& rhs) const { |
| return lhs.mailbox() < rhs.mailbox(); |
| } |
| |
| bool SkiaRenderer::ScopedReadLockComparator::operator()( |
| const DisplayResourceProvider::ScopedReadLockSharedImage& lhs, |
| const gpu::Mailbox& rhs) const { |
| return lhs.mailbox() < rhs; |
| } |
| |
| bool SkiaRenderer::ScopedReadLockComparator::operator()( |
| const gpu::Mailbox& lhs, |
| const DisplayResourceProvider::ScopedReadLockSharedImage& rhs) const { |
| return lhs < rhs.mailbox(); |
| } |
| #endif // defined(OS_APPLE) |
| |
| } // namespace viz |