| /* |
| * Copyright 2014 Google Inc. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <string> |
| #include <vector> |
| |
| #include "base/debug/trace_event.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/math/rect.h" |
| #include "cobalt/math/rect_f.h" |
| #include "cobalt/math/size.h" |
| #include "cobalt/math/size_conversions.h" |
| #include "cobalt/math/transform_2d.h" |
| #include "cobalt/render_tree/border.h" |
| #include "cobalt/render_tree/brush_visitor.h" |
| #include "cobalt/render_tree/color_rgba.h" |
| #include "cobalt/render_tree/composition_node.h" |
| #include "cobalt/render_tree/filter_node.h" |
| #include "cobalt/render_tree/image_node.h" |
| #include "cobalt/render_tree/matrix_transform_node.h" |
| #include "cobalt/render_tree/rect_node.h" |
| #include "cobalt/render_tree/rect_shadow_node.h" |
| #include "cobalt/render_tree/rounded_corners.h" |
| #include "cobalt/render_tree/text_node.h" |
| #include "cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h" |
| #include "cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.h" |
| #include "cobalt/renderer/rasterizer/skia/font.h" |
| #include "cobalt/renderer/rasterizer/skia/glyph_buffer.h" |
| #include "cobalt/renderer/rasterizer/skia/image.h" |
| #include "cobalt/renderer/rasterizer/skia/skia/src/effects/SkNV122RGBShader.h" |
| #include "cobalt/renderer/rasterizer/skia/skia/src/effects/SkYUV2RGBShader.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/skia/include/core/SkTypeface.h" |
| #include "third_party/skia/include/effects/SkBlurImageFilter.h" |
| #include "third_party/skia/include/effects/SkBlurMaskFilter.h" |
| #include "third_party/skia/include/effects/SkDropShadowImageFilter.h" |
| #include "third_party/skia/include/effects/SkGradientShader.h" |
| |
| // Setting this define to 1 will enable TRACE_EVENT calls to be made from |
| // all render node visitations. It is by default set to 0 because it generates |
| // enough calls that performance is affected. |
| #define ENABLE_RENDER_TREE_VISITOR_TRACING 0 |
| |
| // Setting this define to 1 will result in a SkCanvas::flush() call being made |
| // after each node is visited. This is useful for debugging Skia code since |
| // otherwise the actual execution of Skia commands will likely be queued and |
| // deferred for later execution by skia. |
| #define ENABLE_FLUSH_AFTER_EVERY_NODE 0 |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace skia { |
| |
| RenderTreeNodeVisitor::RenderTreeNodeVisitor( |
| SkCanvas* render_target, |
| const CreateScratchSurfaceFunction* create_scratch_surface_function, |
| const base::Closure& reset_skia_context_function, |
| SurfaceCacheDelegate* surface_cache_delegate, |
| common::SurfaceCache* surface_cache, Type visitor_type) |
| : draw_state_(render_target), |
| create_scratch_surface_function_(create_scratch_surface_function), |
| surface_cache_delegate_(surface_cache_delegate), |
| surface_cache_(surface_cache), |
| visitor_type_(visitor_type), |
| reset_skia_context_function_(reset_skia_context_function) { |
| DCHECK_EQ(surface_cache_delegate_ == NULL, surface_cache_ == NULL); |
| if (surface_cache_delegate_) { |
| // Update our surface cache delegate to point to this render tree node |
| // visitor and our draw state. |
| surface_cache_scoped_context_.emplace(surface_cache_delegate_, |
| &draw_state_); |
| } |
| } |
| |
| namespace { |
| // Returns whether the specified node is within the canvas' bounds or not. |
| bool NodeIsWithinCanvasBounds(const SkMatrix& total_matrix, |
| const SkRect& canvas_bounds, |
| const render_tree::Node& node) { |
| SkRect sk_child_bounds(CobaltRectFToSkiaRect(node.GetBounds())); |
| SkRect sk_child_bounds_absolute; |
| |
| // Use the total matrix to compute the node's bounding rectangle in the |
| // canvas' coordinates. |
| total_matrix.mapRect(&sk_child_bounds_absolute, sk_child_bounds); |
| |
| // Return if the node's bounding rectangle intersects with the canvas' |
| // bounding rectangle. |
| return sk_child_bounds_absolute.intersect(canvas_bounds); |
| } |
| } // namespace |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::CompositionNode* composition_node) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "Visit(CompositionNode)"); |
| #endif |
| |
| common::SurfaceCache::Block cache_block(surface_cache_, composition_node); |
| if (cache_block.Cached()) return; |
| |
| const render_tree::CompositionNode::Children& children = |
| composition_node->data().children(); |
| |
| if (children.empty()) { |
| return; |
| } |
| |
| draw_state_.render_target->translate(composition_node->data().offset().x(), |
| composition_node->data().offset().y()); |
| |
| // If we have more than one child (there is little to be gained by performing |
| // these calculations otherwise since our bounding rectangle is equal to |
| // our child's), retrieve our current total matrix and canvas viewport |
| // rectangle so that we can later check if each child is within or outside |
| // the viewport. |
| base::optional<SkRect> canvas_bounds; |
| base::optional<SkMatrix> total_matrix; |
| if (children.size() > 1) { |
| SkIRect canvas_boundsi; |
| draw_state_.render_target->getClipDeviceBounds(&canvas_boundsi); |
| canvas_bounds = SkRect::Make(canvas_boundsi); |
| total_matrix = draw_state_.render_target->getTotalMatrix(); |
| } |
| |
| for (render_tree::CompositionNode::Children::const_iterator iter = |
| children.begin(); |
| iter != children.end(); ++iter) { |
| // No need to proceed if the child node is outside our canvas' viewport |
| // rectangle. |
| if (!canvas_bounds || |
| NodeIsWithinCanvasBounds(*total_matrix, *canvas_bounds, **iter)) { |
| (*iter)->Accept(this); |
| } |
| } |
| |
| draw_state_.render_target->translate(-composition_node->data().offset().x(), |
| -composition_node->data().offset().y()); |
| |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| } |
| |
| namespace { |
| SkRRect RoundedRectToSkia(const math::RectF& rect, |
| const render_tree::RoundedCorners& rounded_corners) { |
| SkVector radii[4] = { |
| {rounded_corners.top_left.horizontal, rounded_corners.top_left.vertical}, |
| {rounded_corners.top_right.horizontal, |
| rounded_corners.top_right.vertical}, |
| {rounded_corners.bottom_right.horizontal, |
| rounded_corners.bottom_right.vertical}, |
| {rounded_corners.bottom_left.horizontal, |
| rounded_corners.bottom_left.vertical}, |
| }; |
| |
| SkRRect rrect; |
| rrect.setRectRadii( |
| SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()), radii); |
| return rrect; |
| } |
| |
| SkPath RoundedRectToSkiaPath( |
| const math::RectF& rect, |
| const render_tree::RoundedCorners& rounded_corners) { |
| SkPath path; |
| path.addRRect(RoundedRectToSkia(rect, rounded_corners), |
| SkPath::kCW_Direction); |
| return path; |
| } |
| |
| void ApplyViewportMask( |
| RenderTreeNodeVisitorDrawState* draw_state, |
| const base::optional<render_tree::ViewportFilter>& filter) { |
| if (!filter) { |
| return; |
| } |
| |
| if (!filter->has_rounded_corners()) { |
| SkRect filter_viewport(CobaltRectFToSkiaRect(filter->viewport())); |
| draw_state->render_target->clipRect(filter_viewport); |
| } else { |
| draw_state->render_target->clipPath( |
| RoundedRectToSkiaPath(filter->viewport(), filter->rounded_corners()), |
| SkRegion::kIntersect_Op, true /* doAntiAlias */); |
| draw_state->clip_is_rect = false; |
| } |
| } |
| |
| SkColor ToSkColor(const render_tree::ColorRGBA& color) { |
| return SkColorSetARGB(color.a() * 255, color.r() * 255, color.g() * 255, |
| color.b() * 255); |
| } |
| |
| void ApplyBlurFilterToPaint( |
| SkPaint* paint, |
| const base::optional<render_tree::BlurFilter>& blur_filter) { |
| if (blur_filter && blur_filter->blur_sigma() > 0.0f) { |
| SkAutoTUnref<SkBlurImageFilter> skia_blur_filter(SkBlurImageFilter::Create( |
| blur_filter->blur_sigma(), blur_filter->blur_sigma())); |
| paint->setImageFilter(skia_blur_filter.get()); |
| } |
| } |
| |
| } // namespace |
| |
| void RenderTreeNodeVisitor::RenderFilterViaOffscreenSurface( |
| const render_tree::FilterNode::Builder& filter_node) { |
| const SkMatrix& total_matrix_skia = |
| draw_state_.render_target->getTotalMatrix(); |
| math::Matrix3F total_matrix = SkiaMatrixToCobalt(total_matrix_skia); |
| |
| SkIRect canvas_boundsi; |
| draw_state_.render_target->getClipDeviceBounds(&canvas_boundsi); |
| |
| common::OffscreenRenderCoordinateMapping coord_mapping = |
| common::GetOffscreenRenderCoordinateMapping( |
| filter_node.GetBounds(), total_matrix, |
| math::Rect(canvas_boundsi.x(), canvas_boundsi.y(), |
| canvas_boundsi.width(), canvas_boundsi.height())); |
| if (coord_mapping.output_bounds.size().GetArea() == 0) { |
| return; |
| } |
| |
| // Create a scratch surface upon which we will render the source subtree. |
| scoped_ptr<ScratchSurface> scratch_surface( |
| create_scratch_surface_function_->Run( |
| math::Size(coord_mapping.output_bounds.width(), |
| coord_mapping.output_bounds.height()))); |
| if (!scratch_surface) { |
| DLOG(ERROR) << "Error creating scratch image surface (width = " |
| << coord_mapping.output_bounds.width() |
| << ", height = " << coord_mapping.output_bounds.height() |
| << "), probably because " |
| "we are low on memory, or the requested dimensions are " |
| "too large or invalid."; |
| // In this case, we quit early and not render anything. |
| return; |
| } |
| |
| // Our canvas is guaranteed to already be cleared to ARGB(0, 0, 0, 0). |
| SkCanvas* canvas = scratch_surface->GetSurface()->getCanvas(); |
| DCHECK(canvas); |
| |
| // Transform our drawing coordinates so we only render the source tree within |
| // the viewport. |
| canvas->setMatrix(CobaltMatrixToSkia(coord_mapping.sub_render_transform)); |
| |
| // Render our source sub-tree into the offscreen surface. |
| { |
| RenderTreeNodeVisitor sub_visitor( |
| canvas, create_scratch_surface_function_, reset_skia_context_function_, |
| surface_cache_delegate_, surface_cache_, kType_SubVisitor); |
| filter_node.source->Accept(&sub_visitor); |
| } |
| |
| // With the source subtree rendered to our temporary scratch surface, we |
| // will now render it to our parent render target with an alpha value set to |
| // the opacity value. |
| SkPaint paint; |
| if (filter_node.opacity_filter) { |
| paint.setARGB(filter_node.opacity_filter->opacity() * 255, 255, 255, 255); |
| } |
| // Use nearest neighbor when filtering texture data, since the source and |
| // destination rectangles should be exactly equal. |
| paint.setFilterLevel(SkPaint::kNone_FilterLevel); |
| |
| // We've already used the draw_state_.render_target's scale when rendering to |
| // the offscreen surface, so reset the scale for now. |
| draw_state_.render_target->save(); |
| ApplyBlurFilterToPaint(&paint, filter_node.blur_filter); |
| ApplyViewportMask(&draw_state_, filter_node.viewport_filter); |
| |
| draw_state_.render_target->setMatrix(CobaltMatrixToSkia( |
| math::TranslateMatrix(coord_mapping.output_pre_translate) * total_matrix * |
| math::ScaleMatrix(coord_mapping.output_post_scale))); |
| |
| SkAutoTUnref<SkImage> image( |
| scratch_surface->GetSurface()->newImageSnapshot()); |
| DCHECK(image); |
| |
| SkRect dest_rect = SkRect::MakeXYWH(coord_mapping.output_bounds.x(), |
| coord_mapping.output_bounds.y(), |
| coord_mapping.output_bounds.width(), |
| coord_mapping.output_bounds.height()); |
| |
| SkRect source_rect = SkRect::MakeWH(coord_mapping.output_bounds.width(), |
| coord_mapping.output_bounds.height()); |
| |
| draw_state_.render_target->drawImageRect(image, &source_rect, dest_rect, |
| &paint); |
| |
| // Finally restore our parent render target's original transform for the |
| // next draw call. |
| draw_state_.render_target->restore(); |
| } |
| |
| namespace { |
| // Returns true if we permit this source render tree to be rendered under a |
| // rounded corners filter mask. In general we avoid doing this becaue it can |
| // multiply the number of shaders required by the system, however in some cases |
| // we want to do this still because there is a performance advantage. |
| bool SourceCanRenderWithRoundedCorners(render_tree::Node* source) { |
| // If we are filtering an image, which is a frequent scenario that is worth |
| // optimizing for with a custom shader. |
| if (source->GetTypeId() == base::GetTypeId<render_tree::ImageNode>()) { |
| return true; |
| } else if (source->GetTypeId() == |
| base::GetTypeId<render_tree::CompositionNode>()) { |
| // If we are a composition of valid sources, then we also allow |
| // rendering through a viewport here. |
| render_tree::CompositionNode* composition_node = |
| base::polymorphic_downcast<render_tree::CompositionNode*>(source); |
| typedef render_tree::CompositionNode::Children Children; |
| const Children& children = composition_node->data().children(); |
| |
| for (Children::const_iterator iter = children.begin(); |
| iter != children.end(); ++iter) { |
| if (!SourceCanRenderWithRoundedCorners(iter->get())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool SourceCanRenderWithOpacity(render_tree::Node* source) { |
| if (source->GetTypeId() == base::GetTypeId<render_tree::ImageNode>() || |
| source->GetTypeId() == base::GetTypeId<render_tree::RectNode>()) { |
| return true; |
| } else if (source->GetTypeId() == |
| base::GetTypeId<render_tree::CompositionNode>()) { |
| // If we are a composition of valid sources, then we also allow |
| // rendering through a viewport here. |
| render_tree::CompositionNode* composition_node = |
| base::polymorphic_downcast<render_tree::CompositionNode*>(source); |
| typedef render_tree::CompositionNode::Children Children; |
| const Children& children = composition_node->data().children(); |
| if (children.size() == 1 && SourceCanRenderWithOpacity(children[0].get())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } // namespace |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::FilterNode* filter_node) { |
| if (filter_node->data().map_to_mesh_filter) { |
| // TODO: Implement support for MapToMeshFilter instead of punching out |
| // the area that it occupies. |
| SkPaint paint; |
| paint.setXfermodeMode(SkXfermode::kSrc_Mode); |
| paint.setARGB(0, 0, 0, 0); |
| |
| math::RectF bounds = filter_node->GetBounds(); |
| SkRect sk_rect = SkRect::MakeXYWH(bounds.x(), bounds.y(), bounds.width(), |
| bounds.height()); |
| |
| draw_state_.render_target->drawRect(sk_rect, paint); |
| |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| |
| return; |
| } |
| |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "Visit(FilterNode)"); |
| |
| if (filter_node->data().opacity_filter) { |
| TRACE_EVENT_INSTANT1("cobalt::renderer", "opacity", "opacity", |
| filter_node->data().opacity_filter->opacity()); |
| } |
| if (filter_node->data().viewport_filter) { |
| TRACE_EVENT_INSTANT2( |
| "cobalt::renderer", "viewport", "width", |
| filter_node->data().viewport_filter->viewport().width(), "height", |
| filter_node->data().viewport_filter->viewport().height()); |
| } |
| if (filter_node->data().blur_filter) { |
| TRACE_EVENT_INSTANT1("cobalt::renderer", "blur", "blur_sigma", |
| filter_node->data().blur_filter->blur_sigma()); |
| } |
| #endif // ENABLE_RENDER_TREE_VISITOR_TRACING |
| |
| if (filter_node->data().opacity_filter && |
| filter_node->data().opacity_filter->opacity() <= 0.0f) { |
| // The opacity 0, so we have nothing to render. |
| return; |
| } |
| |
| if ((!filter_node->data().opacity_filter || |
| filter_node->data().opacity_filter->opacity() == 1.0f) && |
| !filter_node->data().viewport_filter && |
| (!filter_node->data().blur_filter || |
| filter_node->data().blur_filter->blur_sigma() == 0.0f)) { |
| // No filter was set, so just rasterize the source render tree as normal. |
| filter_node->data().source->Accept(this); |
| return; |
| } |
| |
| const SkMatrix& total_matrix = draw_state_.render_target->getTotalMatrix(); |
| |
| bool has_rounded_corners = |
| filter_node->data().viewport_filter && |
| filter_node->data().viewport_filter->has_rounded_corners(); |
| |
| // Our general strategy for applying the filter is to render to a texture |
| // and then render that texture with a viewport mask applied. However Skia |
| // can optimize for us if we render the subtree source directly with a mask |
| // applied. This has the potential to greatly increase the total number of |
| // shaders we need to support, so we only take advantage of this in certain |
| // cases. |
| bool can_render_with_clip_mask_directly = |
| !filter_node->data().blur_filter && |
| // If an opacity filter is being applied, we must render to a separate |
| // texture first. |
| (!filter_node->data().opacity_filter || |
| SourceCanRenderWithOpacity(filter_node->data().source)) && |
| // If transforms are applied to the viewport, then we will render to |
| // a separate texture first. |
| total_matrix.rectStaysRect() && |
| // If the viewport mask has rounded corners, in general we will render |
| // to a bitmap before applying the mask in order to limit the number of |
| // shader permutations (brush types * mask types). However there are |
| // some exceptions, for performance reasons. |
| (!has_rounded_corners || |
| SourceCanRenderWithRoundedCorners(filter_node->data().source)); |
| |
| if (can_render_with_clip_mask_directly) { |
| RenderTreeNodeVisitorDrawState original_draw_state(draw_state_); |
| |
| draw_state_.render_target->save(); |
| // Remember the value of |clip_is_rect| because ApplyViewportMask may |
| // modify it. |
| bool clip_was_rect = draw_state_.clip_is_rect; |
| ApplyViewportMask(&draw_state_, filter_node->data().viewport_filter); |
| |
| if (filter_node->data().opacity_filter) { |
| draw_state_.opacity *= filter_node->data().opacity_filter->opacity(); |
| } |
| filter_node->data().source->Accept(this); |
| |
| draw_state_.clip_is_rect = clip_was_rect; |
| draw_state_.render_target->restore(); |
| draw_state_ = original_draw_state; |
| } else { |
| common::SurfaceCache::Block cache_block(surface_cache_, filter_node); |
| if (cache_block.Cached()) return; |
| |
| RenderFilterViaOffscreenSurface(filter_node->data()); |
| } |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| } |
| |
| namespace { |
| |
| // Skia expects local texture coordinate transform matrices to map from image |
| // space coordinates to output rectangle coordinates, i.e. map a texture to |
| // its output rectangle. Since render_tree local coordinates assume they are |
| // operating in a normalized coordinate system, we must perform some |
| // transformations. |
| void ConvertLocalTransformMatrixToSkiaShaderFormat( |
| const math::Size& input_size, |
| const math::RectF& output_rect, |
| SkMatrix* local_transform_matrix) { |
| // First transform to normalized coordinates, where the input transform |
| // specified by local_transform_matrix is expecting to take place. |
| local_transform_matrix->preScale( |
| 1.0f / static_cast<float>(input_size.width()), |
| 1.0f / static_cast<float>(input_size.height())); |
| |
| // After our transformation is applied, we must then transform to the |
| // output rectangle space. |
| local_transform_matrix->postScale(output_rect.width(), output_rect.height()); |
| local_transform_matrix->postTranslate(output_rect.x(), output_rect.y()); |
| } |
| |
| bool IsScaleAndTranslateOnly(const math::Matrix3F& mat) { |
| return mat.Get(0, 1) == 0.0f && mat.Get(1, 0) == 0.0f; |
| } |
| |
| bool LocalCoordsStaysWithinUnitBox(const math::Matrix3F& mat) { |
| return mat.Get(0, 2) <= 0.0f && 1.0f - mat.Get(0, 2) <= mat.Get(0, 0) && |
| mat.Get(1, 2) <= 0.0f && 1.0f - mat.Get(1, 2) <= mat.Get(1, 1); |
| } |
| |
| SkPaint CreateSkPaintForImageRendering( |
| const RenderTreeNodeVisitorDrawState& draw_state, bool is_opaque) { |
| SkPaint paint; |
| paint.setFilterLevel(SkPaint::kLow_FilterLevel); |
| |
| if (draw_state.opacity < 1.0f) { |
| paint.setAlpha(draw_state.opacity * 255); |
| } else if (is_opaque && draw_state.clip_is_rect) { |
| paint.setXfermodeMode(SkXfermode::kSrc_Mode); |
| } |
| |
| return paint; |
| } |
| |
| void RenderSinglePlaneImage(SinglePlaneImage* single_plane_image, |
| RenderTreeNodeVisitorDrawState* draw_state, |
| const math::RectF& destination_rect, |
| const math::Matrix3F* local_transform) { |
| SkPaint paint = CreateSkPaintForImageRendering( |
| *draw_state, single_plane_image->IsOpaque()); |
| |
| // In the most frequent by far case where the normalized transformed image |
| // texture coordinates lie within the unit square, then we must ensure NOT |
| // to interpolate texel values with wrapped texels across the image borders. |
| // This can result in strips of incorrect colors around the edges of images. |
| // Thus, if the normalized texture coordinates lie within the unit box, we |
| // will blit the image using drawBitmapRectToRect() which handles the bleeding |
| // edge problem for us. |
| if (IsScaleAndTranslateOnly(*local_transform) && |
| LocalCoordsStaysWithinUnitBox(*local_transform)) { |
| // Determine the source rectangle, in image texture pixel coordinates. |
| const math::Size& img_size = single_plane_image->GetSize(); |
| |
| float width = img_size.width() / local_transform->Get(0, 0); |
| float height = img_size.height() / local_transform->Get(1, 1); |
| float x = -width * local_transform->Get(0, 2); |
| float y = -height * local_transform->Get(1, 2); |
| |
| SkRect src = SkRect::MakeXYWH(x, y, width, height); |
| |
| draw_state->render_target->drawBitmapRectToRect( |
| single_plane_image->GetBitmap(), &src, |
| CobaltRectFToSkiaRect(destination_rect), &paint); |
| } else { |
| // Use the more general approach which allows arbitrary local texture |
| // coordinate matrices. |
| SkMatrix skia_local_transform = CobaltMatrixToSkia(*local_transform); |
| |
| ConvertLocalTransformMatrixToSkiaShaderFormat( |
| single_plane_image->GetSize(), destination_rect, &skia_local_transform); |
| |
| SkAutoTUnref<SkShader> image_shader(SkShader::CreateBitmapShader( |
| single_plane_image->GetBitmap(), SkShader::kRepeat_TileMode, |
| SkShader::kRepeat_TileMode, &skia_local_transform)); |
| paint.setShader(image_shader); |
| |
| draw_state->render_target->drawRect(CobaltRectFToSkiaRect(destination_rect), |
| paint); |
| } |
| } |
| |
| void RenderMultiPlaneImage(MultiPlaneImage* multi_plane_image, |
| RenderTreeNodeVisitorDrawState* draw_state, |
| const math::RectF& destination_rect, |
| const math::Matrix3F* local_transform) { |
| SkMatrix skia_local_transform = CobaltMatrixToSkia(*local_transform); |
| |
| const SkBitmap& y_bitmap = multi_plane_image->GetBitmap(0); |
| DCHECK(!y_bitmap.isNull()); |
| SkMatrix y_matrix = skia_local_transform; |
| ConvertLocalTransformMatrixToSkiaShaderFormat( |
| math::Size(y_bitmap.width(), y_bitmap.height()), destination_rect, |
| &y_matrix); |
| |
| const SkBitmap& u_bitmap = multi_plane_image->GetBitmap(1); |
| DCHECK(!u_bitmap.isNull()); |
| SkMatrix u_matrix = skia_local_transform; |
| ConvertLocalTransformMatrixToSkiaShaderFormat( |
| math::Size(u_bitmap.width(), u_bitmap.height()), destination_rect, |
| &u_matrix); |
| |
| SkAutoTUnref<SkShader> yuv2rgb_shader; |
| |
| switch (multi_plane_image->GetFormat()) { |
| case render_tree::kMultiPlaneImageFormatYUV2PlaneBT709: |
| yuv2rgb_shader.reset(SkNEW_ARGS( |
| SkNV122RGBShader, |
| (kRec709_SkYUVColorSpace, y_bitmap, y_matrix, u_bitmap, u_matrix))); |
| break; |
| case render_tree::kMultiPlaneImageFormatYUV3PlaneBT709: { |
| const SkBitmap& v_bitmap = multi_plane_image->GetBitmap(2); |
| DCHECK(!v_bitmap.isNull()); |
| SkMatrix v_matrix = skia_local_transform; |
| ConvertLocalTransformMatrixToSkiaShaderFormat( |
| math::Size(v_bitmap.width(), v_bitmap.height()), destination_rect, |
| &v_matrix); |
| yuv2rgb_shader.reset(SkNEW_ARGS( |
| SkYUV2RGBShader, (kRec709_SkYUVColorSpace, y_bitmap, y_matrix, |
| u_bitmap, u_matrix, v_bitmap, v_matrix))); |
| break; |
| } |
| default: { |
| NOTREACHED() << "Unsupported multi plane image format."; |
| break; |
| } |
| } |
| |
| SkPaint paint = CreateSkPaintForImageRendering(*draw_state, |
| multi_plane_image->IsOpaque()); |
| paint.setShader(yuv2rgb_shader); |
| draw_state->render_target->drawRect(CobaltRectFToSkiaRect(destination_rect), |
| paint); |
| } |
| |
| } // namespace |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::ImageNode* image_node) { |
| // The image_node may contain nothing. For example, when it represents a video |
| // element before any frame is decoded. |
| if (!image_node->data().source) { |
| return; |
| } |
| |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "Visit(ImageNode)"); |
| #endif |
| skia::Image* image = |
| base::polymorphic_downcast<skia::Image*>(image_node->data().source.get()); |
| |
| // Creating an image via a resource provider may simply return a frontend |
| // image object and enqueue the initialization of a backend image (to be |
| // performed on the rasterizer thread). This function ensures that that |
| // initialization is completed. This would normally not be an issue if |
| // an image is submitted as part of a render tree submission, however we |
| // may end up with a non-backend-initialized image if a new image is selected |
| // during an animation callback (e.g. video playback can do this). |
| // This should be a quick operation, and it needs to happen eventually, so |
| // if it is not done now, it will be done in the next frame, or the one after |
| // that. |
| if (image->EnsureInitialized()) { |
| // EnsureInitialized() may make a number of GL calls that results in GL |
| // state being modified behind Skia's back, therefore we have Skia reset its |
| // state in this case. |
| if (!reset_skia_context_function_.is_null()) { |
| reset_skia_context_function_.Run(); |
| } |
| } |
| |
| // We issue different skia rasterization commands to render the image |
| // depending on whether it's single or multi planed. |
| if (image->GetTypeId() == base::GetTypeId<SinglePlaneImage>()) { |
| RenderSinglePlaneImage(base::polymorphic_downcast<SinglePlaneImage*>(image), |
| &draw_state_, image_node->data().destination_rect, |
| &(image_node->data().local_transform)); |
| } else if (image->GetTypeId() == base::GetTypeId<MultiPlaneImage>()) { |
| RenderMultiPlaneImage(base::polymorphic_downcast<MultiPlaneImage*>(image), |
| &draw_state_, image_node->data().destination_rect, |
| &(image_node->data().local_transform)); |
| } else { |
| NOTREACHED(); |
| } |
| |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| } |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::MatrixTransformNode* matrix_transform_node) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "Visit(MatrixTransformNode)"); |
| #endif |
| |
| common::SurfaceCache::Block cache_block(surface_cache_, |
| matrix_transform_node); |
| if (cache_block.Cached()) return; |
| |
| // Concatenate the matrix transform to the render target and then continue |
| // on with rendering our source. |
| draw_state_.render_target->save(); |
| |
| draw_state_.render_target->concat( |
| CobaltMatrixToSkia(matrix_transform_node->data().transform)); |
| |
| // Since our scale may have changed, inform the surface cache system to update |
| // its scale. |
| if (surface_cache_delegate_) { |
| surface_cache_delegate_->UpdateCanvasScale(); |
| } |
| |
| matrix_transform_node->data().source->Accept(this); |
| |
| draw_state_.render_target->restore(); |
| |
| if (surface_cache_delegate_) { |
| surface_cache_delegate_->UpdateCanvasScale(); |
| } |
| |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| } |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::PunchThroughVideoNode* punch_through_video_node) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "Visit(PunchThroughVideoNode)"); |
| #endif |
| |
| if (visitor_type_ == kType_SubVisitor) { |
| DLOG(ERROR) << "Punch through alpha images cannot be rendered via " |
| "offscreen surfaces (since the offscreen surface will then " |
| "be composed using blending). Unfortunately, you will have " |
| "to find another way to arrange your render tree if you " |
| "wish to use punch through alpha (e.g. ensure no opacity " |
| "filters exist as an ancestor to this node)."; |
| // We proceed anyway, just in case things happen to work out. |
| } |
| |
| const math::RectF& math_rect = punch_through_video_node->data().rect; |
| |
| SkRect sk_rect = SkRect::MakeXYWH(math_rect.x(), math_rect.y(), |
| math_rect.width(), math_rect.height()); |
| SkMatrix total_matrix = draw_state_.render_target->getTotalMatrix(); |
| |
| SkRect sk_rect_transformed; |
| total_matrix.mapRect(&sk_rect_transformed, sk_rect); |
| |
| if (punch_through_video_node->data().set_bounds_cb.is_null()) { |
| return; |
| } |
| bool render_punch_through = |
| punch_through_video_node->data().set_bounds_cb.Run( |
| math::Rect(static_cast<int>(sk_rect_transformed.x()), |
| static_cast<int>(sk_rect_transformed.y()), |
| static_cast<int>(sk_rect_transformed.width()), |
| static_cast<int>(sk_rect_transformed.height()))); |
| if (!render_punch_through) { |
| return; |
| } |
| |
| SkPaint paint; |
| paint.setXfermodeMode(SkXfermode::kSrc_Mode); |
| paint.setARGB(0, 0, 0, 0); |
| |
| draw_state_.render_target->drawRect(sk_rect, paint); |
| |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| } |
| |
| namespace { |
| |
| class SkiaBrushVisitor : public render_tree::BrushVisitor { |
| public: |
| explicit SkiaBrushVisitor(SkPaint* paint, |
| const RenderTreeNodeVisitorDrawState& draw_state) |
| : paint_(paint), draw_state_(draw_state) {} |
| |
| void Visit( |
| const cobalt::render_tree::SolidColorBrush* solid_color_brush) OVERRIDE; |
| void Visit(const cobalt::render_tree::LinearGradientBrush* |
| linear_gradient_brush) OVERRIDE; |
| void Visit(const cobalt::render_tree::RadialGradientBrush* |
| radial_gradient_brush) OVERRIDE; |
| |
| private: |
| SkPaint* paint_; |
| const RenderTreeNodeVisitorDrawState& draw_state_; |
| }; |
| |
| void SkiaBrushVisitor::Visit( |
| const cobalt::render_tree::SolidColorBrush* solid_color_brush) { |
| const cobalt::render_tree::ColorRGBA& color = solid_color_brush->color(); |
| |
| float alpha = color.a() * draw_state_.opacity; |
| if (alpha == 1.0f) { |
| paint_->setXfermodeMode(SkXfermode::kSrc_Mode); |
| } else { |
| paint_->setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| } |
| |
| paint_->setARGB(alpha * 255, color.r() * 255, color.g() * 255, |
| color.b() * 255); |
| } |
| |
| namespace { |
| |
| // Helper struct to convert render_tree::ColorStopList to a format that Skia |
| // methods will easily accept. |
| struct SkiaColorStops { |
| explicit SkiaColorStops(const render_tree::ColorStopList& color_stops) |
| : colors(color_stops.size()), |
| positions(color_stops.size()), |
| has_alpha(false) { |
| for (size_t i = 0; i < color_stops.size(); ++i) { |
| if (color_stops[i].color.a() < 1.0f) { |
| has_alpha = true; |
| } |
| |
| colors[i] = ToSkColor(color_stops[i].color); |
| positions[i] = color_stops[i].position; |
| } |
| } |
| |
| std::vector<SkColor> colors; |
| std::vector<SkScalar> positions; |
| bool has_alpha; |
| }; |
| |
| } // namespace |
| |
| void SkiaBrushVisitor::Visit( |
| const cobalt::render_tree::LinearGradientBrush* linear_gradient_brush) { |
| SkPoint points[2] = { |
| {linear_gradient_brush->source().x(), |
| linear_gradient_brush->source().y()}, |
| {linear_gradient_brush->dest().x(), linear_gradient_brush->dest().y()}}; |
| |
| SkiaColorStops skia_color_stops(linear_gradient_brush->color_stops()); |
| |
| SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear( |
| points, &skia_color_stops.colors[0], &skia_color_stops.positions[0], |
| linear_gradient_brush->color_stops().size(), SkShader::kClamp_TileMode, |
| SkGradientShader::kInterpolateColorsInPremul_Flag, NULL)); |
| paint_->setShader(shader); |
| |
| if (!skia_color_stops.has_alpha && draw_state_.opacity == 1.0f) { |
| paint_->setXfermodeMode(SkXfermode::kSrc_Mode); |
| } else { |
| if (draw_state_.opacity < 1.0f) { |
| paint_->setAlpha(255 * draw_state_.opacity); |
| } |
| paint_->setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| } |
| } |
| |
| void SkiaBrushVisitor::Visit( |
| const cobalt::render_tree::RadialGradientBrush* radial_gradient_brush) { |
| SkPoint center = {radial_gradient_brush->center().x(), |
| radial_gradient_brush->center().y()}; |
| |
| SkiaColorStops skia_color_stops(radial_gradient_brush->color_stops()); |
| |
| // Skia accepts parameters for a circular gradient only, but it does accept |
| // a local matrix that we can use to stretch it into an ellipse, so we set |
| // up our circular radius based on our x radius and then stretch the gradient |
| // vertically to the y radius. |
| SkMatrix local_matrix; |
| local_matrix.setIdentity(); |
| float scale_y = |
| radial_gradient_brush->radius_y() / radial_gradient_brush->radius_x(); |
| // And we'll need to add a translation as well to keep the center at the |
| // center despite the scale. |
| float translate_y = (1.0f - scale_y) * radial_gradient_brush->center().y(); |
| local_matrix.setTranslateY(translate_y); |
| local_matrix.setScaleY(scale_y); |
| |
| SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial( |
| center, radial_gradient_brush->radius_x(), &skia_color_stops.colors[0], |
| &skia_color_stops.positions[0], |
| radial_gradient_brush->color_stops().size(), SkShader::kClamp_TileMode, |
| SkGradientShader::kInterpolateColorsInPremul_Flag, &local_matrix)); |
| paint_->setShader(shader); |
| |
| if (!skia_color_stops.has_alpha && draw_state_.opacity == 1.0f) { |
| paint_->setXfermodeMode(SkXfermode::kSrc_Mode); |
| } else { |
| if (draw_state_.opacity < 1.0f) { |
| paint_->setAlpha(255 * draw_state_.opacity); |
| } |
| paint_->setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| } |
| } |
| |
| void DrawRectWithBrush(RenderTreeNodeVisitorDrawState* draw_state, |
| cobalt::render_tree::Brush* brush, |
| const math::RectF& rect) { |
| // Setup our paint object according to the brush parameters set on the |
| // rectangle. |
| SkPaint paint; |
| SkiaBrushVisitor brush_visitor(&paint, *draw_state); |
| brush->Accept(&brush_visitor); |
| |
| draw_state->render_target->drawRect( |
| SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()), paint); |
| } |
| |
| namespace { |
| class CheckForSolidBrushVisitor : public render_tree::BrushVisitor { |
| public: |
| CheckForSolidBrushVisitor() : is_solid_brush_(false) {} |
| |
| void Visit( |
| const cobalt::render_tree::SolidColorBrush* solid_color_brush) OVERRIDE { |
| is_solid_brush_ = true; |
| } |
| |
| void Visit(const cobalt::render_tree::LinearGradientBrush* |
| linear_gradient_brush) OVERRIDE {} |
| void Visit(const cobalt::render_tree::RadialGradientBrush* |
| radial_gradient_brush) OVERRIDE {} |
| |
| bool is_solid_brush() const { return is_solid_brush_; } |
| |
| private: |
| bool is_solid_brush_; |
| }; |
| |
| // DChecks that the brush is a solid brush. |
| bool CheckForSolidBrush(cobalt::render_tree::Brush* brush) { |
| CheckForSolidBrushVisitor visitor; |
| brush->Accept(&visitor); |
| return visitor.is_solid_brush(); |
| } |
| } // namespace |
| |
| void DrawRoundedRectWithBrush( |
| RenderTreeNodeVisitorDrawState* draw_state, render_tree::Brush* brush, |
| const math::RectF& rect, |
| const render_tree::RoundedCorners& rounded_corners) { |
| if (!CheckForSolidBrush(brush)) { |
| NOTREACHED() << "Only solid brushes are currently supported for this shape " |
| "in Cobalt."; |
| return; |
| } |
| |
| SkPaint paint; |
| SkiaBrushVisitor brush_visitor(&paint, *draw_state); |
| brush->Accept(&brush_visitor); |
| |
| paint.setAntiAlias(true); |
| draw_state->render_target->drawPath( |
| RoundedRectToSkiaPath(rect, rounded_corners), paint); |
| } |
| |
| void DrawQuadWithColorIfBorderIsSolid( |
| render_tree::BorderStyle border_style, |
| RenderTreeNodeVisitorDrawState* draw_state, |
| const render_tree::ColorRGBA& color, const SkPoint* points, |
| bool anti_alias) { |
| if (border_style == render_tree::kBorderStyleSolid) { |
| SkPaint paint; |
| float alpha = color.a(); |
| alpha *= draw_state->opacity; |
| paint.setARGB(alpha * 255, color.r() * 255, color.g() * 255, |
| color.b() * 255); |
| paint.setAntiAlias(anti_alias); |
| if (alpha == 1.0f) { |
| paint.setXfermodeMode(SkXfermode::kSrc_Mode); |
| } else { |
| paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| } |
| |
| SkPath path; |
| path.addPoly(points, 4, true); |
| draw_state->render_target->drawPath(path, paint); |
| } else { |
| DCHECK_EQ(border_style, render_tree::kBorderStyleNone); |
| } |
| } |
| |
| // Draw 4 trapezoids for 4 directions. |
| // A ___________ B |
| // |\_________/| |
| // ||E F|| |
| // || || |
| // ||G_______H|| |
| // |/_________\| |
| // C D |
| void DrawSolidNonRoundRectBorder(RenderTreeNodeVisitorDrawState* draw_state, |
| const math::RectF& rect, |
| const render_tree::Border& border) { |
| // Check if the border colors are the same or not to determine whether we |
| // should be using antialiasing. If the border colors are different, then |
| // there will be visible diagonal edges in the output and so we would like to |
| // render with antialiasing enabled to smooth those diagonal edges. |
| bool border_colors_are_same = border.top.color == border.left.color && |
| border.top.color == border.bottom.color && |
| border.top.color == border.right.color; |
| |
| // If any of the border edges have width less than this threshold, they will |
| // use antialiasing as otherwise depending on the border's fractional |
| // position, it may have one extra pixel visible, which is a large percentage |
| // of its small width. |
| const float kAntiAliasWidthThreshold = 3.0f; |
| |
| // Top |
| SkPoint top_points[4] = { |
| {rect.x(), rect.y()}, // A |
| {rect.x() + border.left.width, rect.y() + border.top.width}, // E |
| {rect.right() - border.right.width, rect.y() + border.top.width}, // F |
| {rect.right(), rect.y()}}; // B |
| DrawQuadWithColorIfBorderIsSolid( |
| border.top.style, draw_state, border.top.color, top_points, |
| border.top.width < kAntiAliasWidthThreshold ? true |
| : !border_colors_are_same); |
| |
| // Left |
| SkPoint left_points[4] = { |
| {rect.x(), rect.y()}, // A |
| {rect.x(), rect.bottom()}, // C |
| {rect.x() + border.left.width, rect.bottom() - border.bottom.width}, // G |
| {rect.x() + border.left.width, rect.y() + border.top.width}}; // E |
| DrawQuadWithColorIfBorderIsSolid( |
| border.left.style, draw_state, border.left.color, left_points, |
| border.left.width < kAntiAliasWidthThreshold ? true |
| : !border_colors_are_same); |
| |
| // Bottom |
| SkPoint bottom_points[4] = { |
| {rect.x() + border.left.width, rect.bottom() - border.bottom.width}, // G |
| {rect.x(), rect.bottom()}, // C |
| {rect.right(), rect.bottom()}, // D |
| {rect.right() - border.right.width, |
| rect.bottom() - border.bottom.width}}; // H |
| DrawQuadWithColorIfBorderIsSolid( |
| border.bottom.style, draw_state, border.bottom.color, bottom_points, |
| border.bottom.width < kAntiAliasWidthThreshold ? true |
| : !border_colors_are_same); |
| |
| // Right |
| SkPoint right_points[4] = { |
| {rect.right() - border.right.width, rect.y() + border.top.width}, // F |
| {rect.right() - border.right.width, |
| rect.bottom() - border.bottom.width}, // H |
| {rect.right(), rect.bottom()}, // D |
| {rect.right(), rect.y()}}; // B |
| DrawQuadWithColorIfBorderIsSolid( |
| border.right.style, draw_state, border.right.color, right_points, |
| border.right.width < kAntiAliasWidthThreshold ? true |
| : !border_colors_are_same); |
| } |
| |
| void DrawSolidRoundedRectBorderToRenderTarget( |
| RenderTreeNodeVisitorDrawState* draw_state, const math::RectF& rect, |
| const render_tree::RoundedCorners& rounded_corners, |
| const math::RectF& content_rect, |
| const render_tree::RoundedCorners& inner_rounded_corners, |
| const render_tree::Border& border) { |
| SkPaint paint; |
| paint.setAntiAlias(true); |
| |
| const render_tree::ColorRGBA& color = border.top.color; |
| float alpha = color.a(); |
| alpha *= draw_state->opacity; |
| paint.setARGB(alpha * 255, color.r() * 255, color.g() * 255, color.b() * 255); |
| if (alpha == 1.0f) { |
| paint.setXfermodeMode(SkXfermode::kSrc_Mode); |
| } else { |
| paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| } |
| |
| draw_state->render_target->drawDRRect( |
| RoundedRectToSkia(rect, rounded_corners), |
| RoundedRectToSkia(content_rect, inner_rounded_corners), paint); |
| } |
| |
| void DrawSolidRoundedRectBorderSoftware( |
| RenderTreeNodeVisitorDrawState* draw_state, const math::RectF& rect, |
| const render_tree::RoundedCorners& rounded_corners, |
| const math::RectF& content_rect, |
| const render_tree::RoundedCorners& inner_rounded_corners, |
| const render_tree::Border& border) { |
| SkImageInfo image_info = |
| SkImageInfo::MakeN32(rect.width(), rect.height(), kPremul_SkAlphaType); |
| SkBitmap bitmap; |
| bitmap.allocPixels(image_info); |
| |
| SkCanvas canvas(bitmap); |
| canvas.clear(SkColorSetARGB(0, 0, 0, 0)); |
| |
| math::RectF canvas_rect(rect.size()); |
| math::RectF canvas_content_rect(content_rect); |
| canvas_content_rect.Offset(-rect.OffsetFromOrigin()); |
| |
| RenderTreeNodeVisitorDrawState sub_draw_state(*draw_state); |
| sub_draw_state.render_target = &canvas; |
| |
| DrawSolidRoundedRectBorderToRenderTarget(&sub_draw_state, canvas_rect, |
| rounded_corners, canvas_content_rect, |
| inner_rounded_corners, border); |
| canvas.flush(); |
| |
| SkPaint render_target_paint; |
| render_target_paint.setFilterLevel(SkPaint::kNone_FilterLevel); |
| draw_state->render_target->drawBitmap(bitmap, rect.x(), rect.y(), |
| &render_target_paint); |
| } |
| |
| namespace { |
| bool IsCircle(const math::SizeF& size, |
| const render_tree::RoundedCorners& rounded_corners) { |
| if (size.width() != size.height()) { |
| return false; |
| } |
| |
| float radius = size.width() * 0.5f; |
| // Corners cannot be "more round" than a circle, so we check using >= rather |
| // than == here. |
| return rounded_corners.AllCornersGE( |
| render_tree::RoundedCorner(radius, radius)); |
| } |
| |
| bool ValidateSolidBorderProperties(const render_tree::Border& border) { |
| if ((border.top == border.left) && |
| (border.top == border.right) && |
| (border.top == border.bottom) && |
| (border.top.style == render_tree::kBorderStyleSolid)) { |
| return true; |
| } else { |
| DLOG(ERROR) << "Border sides have different properties: " << border; |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| void DrawSolidRoundedRectBorder( |
| RenderTreeNodeVisitorDrawState* draw_state, const math::RectF& rect, |
| const render_tree::RoundedCorners& rounded_corners, |
| const math::RectF& content_rect, |
| const render_tree::RoundedCorners& inner_rounded_corners, |
| const render_tree::Border& border) { |
| // We only support rendering of rounded corner borders if each side has |
| // the same properties. |
| DCHECK(ValidateSolidBorderProperties(border)); |
| |
| if (IsCircle(rect.size(), rounded_corners)) { |
| // We are able to render circular borders using hardware, so introduce |
| // a special case for them. |
| DrawSolidRoundedRectBorderToRenderTarget(draw_state, rect, rounded_corners, |
| content_rect, |
| inner_rounded_corners, border); |
| } else { |
| // For now we fallback to software for drawing most rounded corner borders, |
| // with some situations specified above being special cased. The reason we |
| // do this is to limit then number of shaders that need to be implemented. |
| NOTIMPLEMENTED() << "Warning: Software rasterizing a solid rectangle " |
| "border."; |
| DrawSolidRoundedRectBorderSoftware(draw_state, rect, rounded_corners, |
| content_rect, inner_rounded_corners, |
| border); |
| } |
| } |
| |
| } // namespace |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::RectNode* rect_node) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "Visit(RectNode)"); |
| #endif |
| const math::RectF& rect(rect_node->data().rect); |
| math::RectF content_rect(rect); |
| if (rect_node->data().border) { |
| content_rect.Inset(rect_node->data().border->left.width, |
| rect_node->data().border->top.width, |
| rect_node->data().border->right.width, |
| rect_node->data().border->bottom.width); |
| } |
| |
| // Apply rounded corners if it exists. |
| base::optional<render_tree::RoundedCorners> inner_rounded_corners; |
| if (rect_node->data().rounded_corners) { |
| if (rect_node->data().border) { |
| inner_rounded_corners = rect_node->data().rounded_corners->Inset( |
| rect_node->data().border->left.width, |
| rect_node->data().border->top.width, |
| rect_node->data().border->right.width, |
| rect_node->data().border->bottom.width); |
| } else { |
| inner_rounded_corners = *rect_node->data().rounded_corners; |
| } |
| } |
| |
| if (rect_node->data().background_brush) { |
| if (inner_rounded_corners && !inner_rounded_corners->AreSquares()) { |
| DrawRoundedRectWithBrush(&draw_state_, |
| rect_node->data().background_brush.get(), |
| content_rect, *inner_rounded_corners); |
| } else { |
| DrawRectWithBrush(&draw_state_, rect_node->data().background_brush.get(), |
| content_rect); |
| } |
| } |
| |
| if (rect_node->data().border) { |
| if (rect_node->data().rounded_corners) { |
| DrawSolidRoundedRectBorder( |
| &draw_state_, rect, *rect_node->data().rounded_corners, content_rect, |
| *inner_rounded_corners, *rect_node->data().border); |
| } else { |
| DrawSolidNonRoundRectBorder(&draw_state_, rect, |
| *rect_node->data().border); |
| } |
| } |
| |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| } |
| |
| namespace { |
| struct RRect { |
| RRect(const math::RectF& rect, |
| const base::optional<render_tree::RoundedCorners>& rounded_corners) |
| : rect(rect), rounded_corners(rounded_corners) {} |
| |
| void Offset(const math::Vector2dF& offset) { rect.Offset(offset); } |
| |
| void Inset(float value) { |
| rect.Inset(value, value); |
| if (rounded_corners) { |
| *rounded_corners = rounded_corners->Inset(value, value, value, value); |
| } |
| } |
| |
| math::RectF rect; |
| base::optional<render_tree::RoundedCorners> rounded_corners; |
| }; |
| |
| // |shadow_rect| contains the original rect, offset according to the shadow's |
| // offset, and expanded by the |spread| parameter. This is the rectangle that |
| // will actually be passed to the draw command. |
| RRect GetShadowRect(const render_tree::RectShadowNode& node) { |
| RRect shadow_rect(node.data().rect, node.data().rounded_corners); |
| |
| shadow_rect.Offset(node.data().shadow.offset); |
| float spread = node.data().spread; |
| if (node.data().inset) { |
| shadow_rect.Inset(spread); |
| } else { |
| shadow_rect.Inset(-spread); |
| } |
| |
| return shadow_rect; |
| } |
| |
| SkPaint GetPaintForBoxShadow(const render_tree::Shadow& shadow) { |
| SkPaint paint; |
| paint.setARGB(shadow.color.a() * 255, shadow.color.r() * 255, |
| shadow.color.g() * 255, shadow.color.b() * 255); |
| |
| SkAutoTUnref<SkMaskFilter> mf( |
| SkBlurMaskFilter::Create(kNormal_SkBlurStyle, shadow.blur_sigma)); |
| paint.setMaskFilter(mf.get()); |
| |
| return paint; |
| } |
| |
| void DrawInsetRectShadowNode(render_tree::RectShadowNode* node, |
| SkCanvas* canvas) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "DrawInsetRectShadowNode()"); |
| #endif |
| DCHECK(node->data().inset); |
| |
| // We outset the user specified shadow rectangle by the blur extent to know |
| // where we should start drawing in order for the blurred result to not appear |
| // light at the edges. There is no point in drawing outside of this rectangle |
| // given the fact that all rendering output will be clipped by |
| // |node->data().size|. |
| const math::RectF& rect = node->data().rect; |
| math::RectF shadow_bounds(rect); |
| math::Vector2dF blur_extent = node->data().shadow.BlurExtent(); |
| shadow_bounds.Outset(blur_extent.x(), blur_extent.y()); |
| |
| // Ensure that |shadow_rect| fits inside of |shadow_bounds| before continuing. |
| math::RectF shadow_rect(GetShadowRect(*node).rect); |
| shadow_rect.Intersect(shadow_bounds); |
| |
| // For inset shadows, we draw 4 rectangles in the shadow's color, all of which |
| // wrap around |shadow_rect|, have extents |shadow_bounds|, and are clipped |
| // by |rect|. |
| SkRect left_shadow_rect = |
| SkRect::MakeLTRB(shadow_bounds.x(), shadow_bounds.y(), shadow_rect.x(), |
| shadow_bounds.bottom()); |
| SkRect top_shadow_rect = |
| SkRect::MakeLTRB(shadow_bounds.x(), shadow_bounds.y(), |
| shadow_bounds.right(), shadow_rect.y()); |
| SkRect right_shadow_rect = |
| SkRect::MakeLTRB(shadow_rect.right(), shadow_bounds.y(), |
| shadow_bounds.right(), shadow_bounds.bottom()); |
| SkRect bottom_shadow_rect = |
| SkRect::MakeLTRB(shadow_bounds.x(), shadow_rect.bottom(), |
| shadow_bounds.right(), shadow_bounds.bottom()); |
| |
| SkRect* draw_rects[] = { |
| &left_shadow_rect, &top_shadow_rect, &right_shadow_rect, |
| &bottom_shadow_rect, |
| }; |
| |
| SkRect skia_clip_rect = CobaltRectFToSkiaRect(rect); |
| |
| SkPaint paint = GetPaintForBoxShadow(node->data().shadow); |
| |
| canvas->save(); |
| canvas->clipRect(skia_clip_rect, SkRegion::kIntersect_Op); |
| for (size_t i = 0; i < arraysize(draw_rects); ++i) { |
| canvas->drawRect(*draw_rects[i], paint); |
| } |
| canvas->restore(); |
| } |
| |
| void DrawOutsetRectShadowNode(render_tree::RectShadowNode* node, |
| SkCanvas* canvas) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "DrawOutsetRectShadowNode()"); |
| #endif |
| const math::RectF& rect = node->data().rect; |
| |
| SkRect skia_clip_rect = CobaltRectFToSkiaRect(rect); |
| |
| const math::RectF shadow_rect(GetShadowRect(*node).rect); |
| SkRect skia_draw_rect = CobaltRectFToSkiaRect(shadow_rect); |
| |
| SkPaint paint = GetPaintForBoxShadow(node->data().shadow); |
| |
| if (node->data().inset) { |
| std::swap(skia_clip_rect, skia_draw_rect); |
| } |
| |
| canvas->save(); |
| canvas->clipRect(skia_clip_rect, SkRegion::kDifference_Op, true); |
| canvas->drawRect(skia_draw_rect, paint); |
| canvas->restore(); |
| } |
| |
| void DrawRoundedRectShadowNode(render_tree::RectShadowNode* node, |
| SkCanvas* canvas) { |
| if (node->data().shadow.blur_sigma > 0.0f) { |
| NOTIMPLEMENTED() << "Cobalt doesn't currently support rendering blurred " |
| "box shadows on rounded rectangles."; |
| return; |
| } |
| if (!IsCircle(node->data().rect.size(), *node->data().rounded_corners)) { |
| NOTIMPLEMENTED() << "Cobalt does not support box shadows with rounded " |
| "corners that are not circles."; |
| return; |
| } |
| |
| RRect shadow_rrect = GetShadowRect(*node); |
| |
| SkRRect skia_clip_rrect = |
| RoundedRectToSkia(node->data().rect, *node->data().rounded_corners); |
| SkRRect skia_draw_rrect = |
| RoundedRectToSkia(shadow_rrect.rect, *shadow_rrect.rounded_corners); |
| |
| if (node->data().inset) { |
| std::swap(skia_clip_rrect, skia_draw_rrect); |
| } |
| |
| canvas->save(); |
| |
| canvas->clipRRect(skia_clip_rrect, SkRegion::kDifference_Op, true); |
| |
| SkPaint paint = GetPaintForBoxShadow(node->data().shadow); |
| paint.setAntiAlias(true); |
| canvas->drawRRect(skia_draw_rrect, paint); |
| |
| canvas->restore(); |
| } |
| |
| } // namespace |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::RectShadowNode* rect_shadow_node) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "Visit(RectShadowNode)"); |
| #endif |
| DCHECK_EQ(1.0f, draw_state_.opacity); |
| common::SurfaceCache::Block cache_block(surface_cache_, rect_shadow_node); |
| if (cache_block.Cached()) return; |
| |
| if (rect_shadow_node->data().rounded_corners) { |
| DrawRoundedRectShadowNode(rect_shadow_node, draw_state_.render_target); |
| } else { |
| if (rect_shadow_node->data().inset) { |
| DrawInsetRectShadowNode(rect_shadow_node, draw_state_.render_target); |
| } else { |
| DrawOutsetRectShadowNode(rect_shadow_node, draw_state_.render_target); |
| } |
| } |
| |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| } |
| |
| namespace { |
| void RenderText(SkCanvas* render_target, |
| const scoped_refptr<render_tree::GlyphBuffer>& glyph_buffer, |
| const render_tree::ColorRGBA& color, |
| const math::PointF& position, float blur_sigma) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "RenderText()"); |
| #endif |
| if (blur_sigma > 20.0f) { |
| // TODO: We could easily switch to using a blur filter at this point. |
| // Ideally we would just use a blur filter to do all blurred text |
| // rendering. Unfortunately, performance is currently terrible when |
| // using blur filters. Since we don't use blur filters (instead the |
| // blur is rasterized into the glyph atlas by software), we choose not |
| // to snap to using them when the blur reaches a certain point to |
| // avoid discontinuity in blur appearance and performance issues. |
| NOTIMPLEMENTED() << "Cobalt does not yet support text blurs with Gaussian " |
| "sigmas larger than 20."; |
| } else { |
| GlyphBuffer* skia_glyph_buffer = |
| base::polymorphic_downcast<GlyphBuffer*>(glyph_buffer.get()); |
| |
| SkPaint paint(Font::GetDefaultSkPaint()); |
| paint.setARGB(color.a() * 255, color.r() * 255, color.g() * 255, |
| color.b() * 255); |
| |
| if (blur_sigma > 0.0f) { |
| SkAutoTUnref<SkMaskFilter> mf( |
| SkBlurMaskFilter::Create(kNormal_SkBlurStyle, blur_sigma, |
| SkBlurMaskFilter::kHighQuality_BlurFlag)); |
| paint.setMaskFilter(mf.get()); |
| } |
| |
| SkAutoTUnref<const SkTextBlob> text_blob(skia_glyph_buffer->GetTextBlob()); |
| render_target->drawTextBlob(text_blob.get(), position.x(), position.y(), |
| paint); |
| } |
| } |
| } // namespace |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::TextNode* text_node) { |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| TRACE_EVENT0("cobalt::renderer", "Visit(TextNode)"); |
| #endif |
| DCHECK_EQ(1.0f, draw_state_.opacity); |
| common::SurfaceCache::Block cache_block(surface_cache_, text_node); |
| if (cache_block.Cached()) return; |
| |
| // If blur was used for any of the shadows, apply a little bit of blur |
| // to all of them, to ensure that Skia follows the same path for rendering |
| // them all (i.e. we don't want the main text to use distance field rendering |
| // and the other text use standard bitmap font rendering), which ensures that |
| // they will be pixel-aligned with each other. |
| float blur_zero_sigma = 0.0f; |
| |
| if (text_node->data().shadows) { |
| const std::vector<render_tree::Shadow>& shadows = |
| *text_node->data().shadows; |
| |
| for (size_t i = 0; i < shadows.size(); ++i) { |
| if (shadows[i].blur_sigma > 0.0f) { |
| // At least one shadow is using a blur, so set a small blur on all |
| // text renders. |
| blur_zero_sigma = 0.000001f; |
| break; |
| } |
| } |
| |
| int num_shadows = static_cast<int>(shadows.size()); |
| for (int i = num_shadows - 1; i >= 0; --i) { |
| // Shadows are rendered in back to front order. |
| const render_tree::Shadow& shadow = shadows[i]; |
| |
| RenderText( |
| draw_state_.render_target, text_node->data().glyph_buffer, |
| shadow.color, math::PointAtOffsetFromOrigin(text_node->data().offset + |
| shadow.offset), |
| shadow.blur_sigma == 0.0f ? blur_zero_sigma : shadow.blur_sigma); |
| } |
| } |
| |
| // Finally render the main text. |
| RenderText(draw_state_.render_target, text_node->data().glyph_buffer, |
| text_node->data().color, |
| math::PointAtOffsetFromOrigin(text_node->data().offset), |
| blur_zero_sigma); |
| |
| #if ENABLE_FLUSH_AFTER_EVERY_NODE |
| draw_state_.render_target->flush(); |
| #endif |
| } |
| |
| } // namespace skia |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |