| // Copyright 2016 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/blitter/render_tree_node_visitor.h" |
| |
| #include "base/bind.h" |
| #include "base/debug/trace_event.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/math/matrix3_f.h" |
| #include "cobalt/math/rect.h" |
| #include "cobalt/math/rect_f.h" |
| #include "cobalt/math/size.h" |
| #include "cobalt/math/transform_2d.h" |
| #include "cobalt/math/vector2d_f.h" |
| #include "cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h" |
| #include "cobalt/renderer/rasterizer/blitter/image.h" |
| #include "cobalt/renderer/rasterizer/blitter/linear_gradient.h" |
| #include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h" |
| #include "cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h" |
| #include "cobalt/renderer/rasterizer/common/utils.h" |
| #include "starboard/blitter.h" |
| |
| #if SB_HAS(BLITTER) |
| |
| // This define exists so that developers can quickly toggle it temporarily and |
| // obtain trace results for the render tree visit process here. In general |
| // though it slows down tracing too much to leave it enabled. |
| #define ENABLE_RENDER_TREE_VISITOR_TRACING 0 |
| |
| #if ENABLE_RENDER_TREE_VISITOR_TRACING |
| #define TRACE_EVENT0_IF_ENABLED(x) TRACE_EVENT0("cobalt::renderer", x) |
| #else |
| #define TRACE_EVENT0_IF_ENABLED(x) |
| #endif |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace blitter { |
| |
| using math::Matrix3F; |
| using math::Rect; |
| using math::RectF; |
| using math::Size; |
| using math::Vector2dF; |
| using render_tree::Border; |
| using render_tree::Brush; |
| using render_tree::ColorRGBA; |
| using render_tree::ColorStop; |
| using render_tree::ColorStopList; |
| using render_tree::LinearGradientBrush; |
| using render_tree::SolidColorBrush; |
| using render_tree::ViewportFilter; |
| |
| RenderTreeNodeVisitor::RenderTreeNodeVisitor( |
| SbBlitterDevice device, SbBlitterContext context, |
| const RenderState& render_state, ScratchSurfaceCache* scratch_surface_cache, |
| CachedSoftwareRasterizer* software_surface_cache, |
| LinearGradientCache* linear_gradient_cache) |
| : device_(device), |
| context_(context), |
| render_state_(render_state), |
| scratch_surface_cache_(scratch_surface_cache), |
| software_surface_cache_(software_surface_cache), |
| linear_gradient_cache_(linear_gradient_cache) {} |
| |
| namespace { |
| void DrawClearRect(SbBlitterContext context, SbBlitterRect rect, |
| SbBlitterColor color) { |
| SbBlitterSetColor(context, color); |
| SbBlitterSetBlending(context, false); |
| SbBlitterFillRect(context, rect); |
| } |
| |
| SbBlitterColor RenderTreeToBlitterColor(const ColorRGBA& color) { |
| return SbBlitterColorFromRGBA(color.r() * 255, color.g() * 255, |
| color.b() * 255, color.a() * 255); |
| } |
| } // namespace |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::ClearRectNode* clear_rect_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(ClearRectNode)"); |
| SbBlitterRect blitter_rect = RectFToBlitterRect( |
| render_state_.transform.TransformRect(clear_rect_node->data().rect)); |
| |
| DrawClearRect(context_, blitter_rect, |
| RenderTreeToBlitterColor(clear_rect_node->data().color)); |
| } |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::CompositionNode* composition_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(CompositionNode)"); |
| |
| const render_tree::CompositionNode::Children& children = |
| composition_node->data().children(); |
| |
| if (children.empty()) { |
| return; |
| } |
| |
| render_state_.transform.ApplyOffset(composition_node->data().offset()); |
| for (render_tree::CompositionNode::Children::const_iterator iter = |
| children.begin(); |
| iter != children.end(); ++iter) { |
| if (render_state_.transform.TransformRect((*iter)->GetBounds()) |
| .Intersects(RectF(render_state_.bounds_stack.Top()))) { |
| (*iter)->Accept(this); |
| } |
| } |
| render_state_.transform.ApplyOffset(-composition_node->data().offset()); |
| } |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::FilterNode* filter_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(FilterNode)"); |
| |
| if (filter_node->data().blur_filter) { |
| // The Starboard Blitter API does not support blur filters, so we fallback |
| // to software for this. |
| RenderWithSoftwareRenderer(filter_node); |
| return; |
| } |
| |
| render_tree::Node* source = filter_node->data().source.get(); |
| |
| // Will be made active if a viewport filter is set. |
| base::optional<BoundsStack::ScopedPush> scoped_push; |
| |
| if (filter_node->data().viewport_filter) { |
| const ViewportFilter& viewport_filter = |
| *filter_node->data().viewport_filter; |
| |
| if (viewport_filter.has_rounded_corners()) { |
| RenderWithSoftwareRenderer(filter_node); |
| return; |
| } |
| |
| scoped_push.emplace( |
| &render_state_.bounds_stack, |
| cobalt::math::Rect::RoundFromRectF( |
| render_state_.transform.TransformRect(viewport_filter.viewport()))); |
| } |
| |
| if (!filter_node->data().opacity_filter || |
| filter_node->data().opacity_filter->opacity() == 1.0f) { |
| source->Accept(this); |
| } else if (filter_node->data().opacity_filter->opacity() != 0.0f) { |
| // If the opacity is set to 0, the contents are invisible and we are |
| // trivially done. However, if we made it into this branch, then |
| // we know that opacity is in the range (0, 1), exclusive. |
| float opacity = filter_node->data().opacity_filter->opacity(); |
| |
| if (common::utils::NodeCanRenderWithOpacity(source)) { |
| float original_opacity = render_state_.opacity; |
| render_state_.opacity *= opacity; |
| |
| source->Accept(this); |
| |
| render_state_.opacity = original_opacity; |
| return; |
| } |
| |
| // Render our source subtree to an offscreen surface, and then we will |
| // re-render it to our main render target with an alpha value applied to it. |
| scoped_ptr<OffscreenRender> offscreen_render = |
| RenderToOffscreenSurface(source); |
| if (!offscreen_render) { |
| // This can happen if the output area of the source node is 0, in which |
| // case we're trivially done. |
| return; |
| } |
| |
| SbBlitterSurface offscreen_surface = |
| offscreen_render->scratch_surface->GetSurface(); |
| |
| // Now blit our offscreen surface to our main render target with opacity |
| // applied. |
| SbBlitterSetBlending(context_, true); |
| SbBlitterSetModulateBlitsWithColor(context_, true); |
| SbBlitterSetColor( |
| context_, |
| SbBlitterColorFromRGBA(255, 255, 255, static_cast<int>(255 * opacity))); |
| SbBlitterBlitRectToRect( |
| context_, offscreen_surface, |
| SbBlitterMakeRect(0, 0, offscreen_render->destination_rect.width(), |
| offscreen_render->destination_rect.height()), |
| RectFToBlitterRect(offscreen_render->destination_rect)); |
| } |
| } |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::ImageNode* image_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(ImageNode)"); |
| // The image_node may contain nothing. For example, when it represents a video |
| // or other kind of animated image element before any frame is decoded. |
| if (!image_node->data().source) { |
| return; |
| } |
| |
| // All Blitter API images derive from skia::Image (so that they can be |
| // compatible with the Skia software renderer), so we start here by casting |
| // to skia::Image. |
| skia::Image* skia_image = |
| base::polymorphic_downcast<skia::Image*>(image_node->data().source.get()); |
| const Size& image_size = skia_image->GetSize(); |
| |
| if (skia_image->GetTypeId() == base::GetTypeId<skia::MultiPlaneImage>()) { |
| // Let software Skia deal with multiplane (e.g. YUV) image rendering. |
| RenderWithSoftwareRenderer(image_node); |
| return; |
| } |
| |
| const Matrix3F& local_matrix = image_node->data().local_transform; |
| if (local_matrix.Get(1, 0) != 0 || local_matrix.Get(0, 1) != 0) { |
| // The Starboard Blitter API does not support local texture transforms that |
| // involve rotations or shears, so we must fallback to software to perform |
| // these. |
| RenderWithSoftwareRenderer(image_node); |
| return; |
| } |
| |
| // All single-plane images are guaranteed to be of type SinglePlaneImage. |
| SinglePlaneImage* blitter_image = |
| base::polymorphic_downcast<SinglePlaneImage*>(skia_image); |
| |
| // Ensure any required backend processing is done to create the necessary |
| // GPU resource. |
| if (blitter_image->EnsureInitialized()) { |
| // Restore the original render target, since it is possible that rendering |
| // took place to another render target in this call. |
| SbBlitterSetRenderTarget(context_, render_state_.render_target); |
| } |
| |
| // Apply the local image coordinate transform to the source rectangle. Note |
| // that the render tree local transform matrix is normalized, but the Blitter |
| // API source rectangle is specified in pixel units, so we must multiply the |
| // offset by |image_size| in order to get the correct values. |
| Transform local_transform; |
| local_transform.ApplyScale( |
| Vector2dF(1.0f / local_matrix.Get(0, 0), 1.0f / local_matrix.Get(1, 1))); |
| local_transform.ApplyOffset( |
| Vector2dF(-local_matrix.Get(0, 2) * image_size.width(), |
| -local_matrix.Get(1, 2) * image_size.height())); |
| |
| // Render the image. |
| if (render_state_.opacity < 1.0f) { |
| SbBlitterSetBlending(context_, true); |
| SbBlitterSetModulateBlitsWithColor(context_, true); |
| SbBlitterSetColor( |
| context_, |
| SbBlitterColorFromRGBA(255, 255, 255, |
| static_cast<int>(255 * render_state_.opacity))); |
| } else { |
| SbBlitterSetBlending(context_, !skia_image->IsOpaque()); |
| SbBlitterSetModulateBlitsWithColor(context_, false); |
| } |
| |
| SbBlitterBlitRectToRectTiled( |
| context_, blitter_image->surface(), |
| RectFToBlitterRect(local_transform.TransformRect(RectF(image_size))), |
| RectFToBlitterRect(render_state_.transform.TransformRect( |
| image_node->data().destination_rect))); |
| } |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::MatrixTransform3DNode* matrix_transform_3d_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(MatrixTransform3DNode)"); |
| // Ignore the 3D transform matrix, it cannot be implemented within the Blitter |
| // API. |
| matrix_transform_3d_node->data().source->Accept(this); |
| } |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::MatrixTransformNode* matrix_transform_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(MatrixTransformNode)"); |
| |
| const Matrix3F& transform = matrix_transform_node->data().transform; |
| |
| if (transform.Get(1, 0) != 0 || transform.Get(0, 1) != 0 || |
| transform.Get(0, 0) < 0 || transform.Get(1, 1) < 0) { |
| // The Starboard Blitter API does not support rotations/shears/flips, so we |
| // must fallback to software in order to render the entire subtree. |
| RenderWithSoftwareRenderer(matrix_transform_node); |
| return; |
| } |
| |
| Transform old_transform(render_state_.transform); |
| |
| render_state_.transform.ApplyOffset( |
| Vector2dF(transform.Get(0, 2), transform.Get(1, 2))); |
| render_state_.transform.ApplyScale( |
| Vector2dF(transform.Get(0, 0), transform.Get(1, 1))); |
| |
| matrix_transform_node->data().source->Accept(this); |
| |
| render_state_.transform = old_transform; |
| } |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::PunchThroughVideoNode* punch_through_video_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(PunchThroughVideoNode)"); |
| |
| SbBlitterRect blitter_rect = |
| RectFToBlitterRect(render_state_.transform.TransformRect( |
| punch_through_video_node->data().rect)); |
| |
| punch_through_video_node->data().set_bounds_cb.Run( |
| math::Rect(blitter_rect.x, blitter_rect.y, blitter_rect.width, |
| blitter_rect.height)); |
| |
| DrawClearRect(context_, blitter_rect, SbBlitterColorFromRGBA(0, 0, 0, 0)); |
| } |
| |
| namespace { |
| bool AllBorderSidesHaveSameColorAndStyle(const Border& border) { |
| return border.left.style == border.right.style && |
| border.left.style == border.top.style && |
| border.left.style == border.bottom.style && |
| border.left.color == border.right.color && |
| border.left.color == border.top.color && |
| border.left.color == border.bottom.color; |
| } |
| |
| void RenderRectNodeBorder(SbBlitterContext context, ColorRGBA color, float left, |
| float right, float top, float bottom, |
| const RectF& rect) { |
| SbBlitterColor blitter_color = RenderTreeToBlitterColor(color); |
| SbBlitterSetColor(context, blitter_color); |
| |
| if (SbBlitterAFromColor(blitter_color) < 255) { |
| SbBlitterSetBlending(context, true); |
| } else { |
| SbBlitterSetBlending(context, false); |
| } |
| |
| // We draw four rectangles, one for each border edge. They have the following |
| // layout: |
| // |
| // ------------- |
| // | | T | |
| // | |---------| |
| // | | | | |
| // |L| |R| |
| // | | | | |
| // |---------| | |
| // | B | | |
| // ------------- |
| |
| // Left |
| SbBlitterFillRect(context, RectFToBlitterRect(RectF(rect.x(), rect.y(), left, |
| rect.height() - bottom))); |
| // Bottom |
| SbBlitterFillRect(context, |
| RectFToBlitterRect(RectF(rect.x(), rect.bottom() - bottom, |
| rect.width() - right, bottom))); |
| // Right |
| SbBlitterFillRect( |
| context, RectFToBlitterRect(RectF(rect.right() - right, rect.y() + top, |
| right, rect.height() - top))); |
| // Top |
| SbBlitterFillRect( |
| context, RectFToBlitterRect( |
| RectF(rect.x() + left, rect.y(), rect.width() - left, top))); |
| } |
| |
| } // namespace |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::RectNode* rect_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(RectNode)"); |
| |
| if (rect_node->data().rounded_corners) { |
| // We can't render rounded corners through the Blitter API. |
| RenderWithSoftwareRenderer(rect_node); |
| return; |
| } |
| if (rect_node->data().border) { |
| if (!AllBorderSidesHaveSameColorAndStyle(*rect_node->data().border)) { |
| // If the borders don't all have the same color and style, we can't |
| // render them with the Blitter API (because we can't just render 4 |
| // rectangles), so fallback to software. |
| RenderWithSoftwareRenderer(rect_node); |
| return; |
| } |
| } |
| |
| const RectF& transformed_rect = |
| render_state_.transform.TransformRect(rect_node->data().rect); |
| |
| if (rect_node->data().background_brush) { |
| base::TypeId background_brush_typeid( |
| rect_node->data().background_brush->GetTypeId()); |
| |
| if (background_brush_typeid == base::GetTypeId<SolidColorBrush>()) { |
| // Render the solid color fill, if a brush exists. |
| SolidColorBrush* solid_color_brush = |
| base::polymorphic_downcast<SolidColorBrush*>( |
| rect_node->data().background_brush.get()); |
| ColorRGBA color = solid_color_brush->color(); |
| |
| if (render_state_.opacity < 1.0f) { |
| color.set_a(color.a() * render_state_.opacity); |
| } |
| |
| SbBlitterSetBlending(context_, color.a() < 1.0f); |
| SbBlitterSetColor(context_, RenderTreeToBlitterColor(color)); |
| |
| SbBlitterFillRect(context_, RectFToBlitterRect(transformed_rect)); |
| } else if (background_brush_typeid == |
| base::GetTypeId<LinearGradientBrush>()) { |
| DCHECK(rect_node != NULL); |
| bool rendered_gradient = RenderLinearGradient( |
| device_, context_, render_state_, *rect_node, linear_gradient_cache_); |
| if (!rendered_gradient) { |
| RenderWithSoftwareRenderer(rect_node); |
| return; |
| } |
| } else { |
| // If we have a more complicated brush fallback to software. |
| RenderWithSoftwareRenderer(rect_node); |
| return; |
| } |
| } |
| |
| // Render the border, if it exists. |
| if (rect_node->data().border) { |
| const Border& border = *rect_node->data().border; |
| DCHECK(AllBorderSidesHaveSameColorAndStyle(border)); |
| |
| if (border.left.style != render_tree::kBorderStyleNone) { |
| DCHECK_EQ(render_tree::kBorderStyleSolid, border.left.style); |
| |
| float left_width = |
| border.left.width * render_state_.transform.scale().x(); |
| float right_width = |
| border.right.width * render_state_.transform.scale().x(); |
| float top_width = border.top.width * render_state_.transform.scale().y(); |
| float bottom_width = |
| border.bottom.width * render_state_.transform.scale().y(); |
| |
| ColorRGBA color = border.left.color; |
| if (render_state_.opacity < 1.0f) { |
| color.set_a(color.a() * render_state_.opacity); |
| } |
| RenderRectNodeBorder(context_, color, left_width, right_width, top_width, |
| bottom_width, transformed_rect); |
| } |
| } |
| } |
| |
| void RenderTreeNodeVisitor::Visit( |
| render_tree::RectShadowNode* rect_shadow_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(RectShadowNode)"); |
| |
| RenderWithSoftwareRenderer(rect_shadow_node); |
| } |
| |
| void RenderTreeNodeVisitor::Visit(render_tree::TextNode* text_node) { |
| TRACE_EVENT0_IF_ENABLED("Visit(TextNode)"); |
| |
| RenderWithSoftwareRenderer(text_node); |
| } |
| |
| void RenderTreeNodeVisitor::RenderWithSoftwareRenderer( |
| render_tree::Node* node) { |
| TRACE_EVENT0("cobalt::renderer", "RenderWithSoftwareRenderer()"); |
| CachedSoftwareRasterizer::SurfaceReference software_surface_reference( |
| software_surface_cache_, node, render_state_.transform); |
| CachedSoftwareRasterizer::Surface software_surface = |
| software_surface_reference.surface(); |
| if (!SbBlitterIsSurfaceValid(software_surface.surface)) { |
| return; |
| } |
| |
| Transform apply_transform(render_state_.transform); |
| apply_transform.ApplyScale(software_surface.coord_mapping.output_post_scale); |
| math::RectF output_rectf = apply_transform.TransformRect( |
| software_surface.coord_mapping.output_bounds); |
| // We can simulate a "pre-multiply" by translation by offsetting the final |
| // output rectangle by the pre-translate, effectively resulting in the |
| // translation being applied last, as intended. |
| output_rectf.Offset(software_surface.coord_mapping.output_pre_translate); |
| SbBlitterRect output_blitter_rect = RectFToBlitterRect(output_rectf); |
| |
| SbBlitterSetBlending(context_, true); |
| |
| if (render_state_.opacity < 1.0f) { |
| SbBlitterSetModulateBlitsWithColor(context_, true); |
| SbBlitterSetColor( |
| context_, |
| SbBlitterColorFromRGBA(255, 255, 255, |
| static_cast<int>(255 * render_state_.opacity))); |
| } else { |
| SbBlitterSetModulateBlitsWithColor(context_, false); |
| } |
| |
| // Blit the software rasterized surface to our actual render target. |
| #if defined(ENABLE_DEBUG_CONSOLE) |
| if (render_state_.highlight_software_draws && software_surface.created) { |
| SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 255, 0, 255)); |
| SbBlitterFillRect(context_, output_blitter_rect); |
| } else // NOLINT(readability/braces) |
| #endif // defined(ENABLE_DEBUG_CONSOLE) |
| { |
| TRACE_EVENT0("cobalt::renderer", "SbBlitterBlitRectToRect()"); |
| SbBlitterBlitRectToRect( |
| context_, software_surface.surface, |
| SbBlitterMakeRect( |
| 0, 0, software_surface.coord_mapping.output_bounds.width(), |
| software_surface.coord_mapping.output_bounds.height()), |
| output_blitter_rect); |
| } |
| } |
| |
| scoped_ptr<RenderTreeNodeVisitor::OffscreenRender> |
| RenderTreeNodeVisitor::RenderToOffscreenSurface(render_tree::Node* node) { |
| TRACE_EVENT0_IF_ENABLED("RenderToOffscreenSurface()"); |
| |
| common::OffscreenRenderCoordinateMapping coord_mapping = |
| common::GetOffscreenRenderCoordinateMapping( |
| node->GetBounds(), render_state_.transform.ToMatrix(), |
| render_state_.bounds_stack.Top()); |
| if (coord_mapping.output_bounds.IsEmpty()) { |
| // There's nothing to render if the bounds are 0. |
| return scoped_ptr<OffscreenRender>(); |
| } |
| DCHECK_GE(0.001f, std::abs(1.0f - |
| render_state_.transform.scale().x() * |
| coord_mapping.output_post_scale.x())); |
| DCHECK_GE(0.001f, std::abs(1.0f - |
| render_state_.transform.scale().y() * |
| coord_mapping.output_post_scale.y())); |
| |
| scoped_ptr<CachedScratchSurface> scratch_surface(new CachedScratchSurface( |
| scratch_surface_cache_, coord_mapping.output_bounds.size())); |
| SbBlitterSurface surface = scratch_surface->GetSurface(); |
| if (!SbBlitterIsSurfaceValid(surface)) { |
| return scoped_ptr<RenderTreeNodeVisitor::OffscreenRender>(); |
| } |
| |
| SbBlitterRenderTarget render_target = |
| SbBlitterGetRenderTargetFromSurface(surface); |
| |
| SbBlitterSetRenderTarget(context_, render_target); |
| |
| // Render to the sub-surface. |
| RenderTreeNodeVisitor sub_visitor( |
| device_, context_, |
| RenderState( |
| render_target, Transform(coord_mapping.sub_render_transform), |
| BoundsStack(context_, Rect(coord_mapping.output_bounds.size()))), |
| scratch_surface_cache_, software_surface_cache_, linear_gradient_cache_); |
| node->Accept(&sub_visitor); |
| |
| // Restore our original render target. |
| SbBlitterSetRenderTarget(context_, render_state_.render_target); |
| // Restore our context's scissor rectangle to what it was before we switched |
| // render targets. |
| render_state_.bounds_stack.UpdateContext(); |
| |
| math::PointF output_point = coord_mapping.output_bounds.origin() + |
| coord_mapping.output_pre_translate + |
| render_state_.transform.translate(); |
| |
| scoped_ptr<OffscreenRender> ret(new OffscreenRender()); |
| ret->destination_rect = |
| math::RectF(output_point, coord_mapping.output_bounds.size()); |
| ret->scratch_surface = scratch_surface.Pass(); |
| |
| return ret.Pass(); |
| } |
| |
| } // namespace blitter |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |
| |
| #endif // #if SB_HAS(BLITTER) |