| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/gfx/compositor/graph/nodes.h" |
| |
| #include <ostream> |
| |
| #include "base/logging.h" |
| #include "mojo/services/gfx/composition/cpp/formatting.h" |
| #include "mojo/skia/type_converters.h" |
| #include "services/gfx/compositor/graph/scene_content.h" |
| #include "services/gfx/compositor/graph/snapshot.h" |
| #include "services/gfx/compositor/graph/transform_pair.h" |
| #include "services/gfx/compositor/render/render_image.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "third_party/skia/include/core/SkMatrix.h" |
| #include "third_party/skia/include/core/SkPaint.h" |
| #include "third_party/skia/include/core/SkPoint.h" |
| #include "third_party/skia/include/core/SkRect.h" |
| #include "third_party/skia/include/utils/SkMatrix44.h" |
| |
| namespace compositor { |
| namespace { |
| SkColor MakeSkColor(const mojo::gfx::composition::Color& color) { |
| return SkColorSetARGBInline(color.alpha, color.red, color.green, color.blue); |
| } |
| |
| void SetPaintForBlend(SkPaint* paint, mojo::gfx::composition::Blend* blend) { |
| DCHECK(paint); |
| if (blend) |
| paint->setAlpha(blend->alpha); |
| } |
| |
| bool Contains(const SkRect& bounds, const SkPoint& point) { |
| return point.x() >= bounds.left() && point.x() < bounds.right() && |
| point.y() >= bounds.top() && point.y() < bounds.bottom(); |
| } |
| } // namespace |
| |
| Node::Node(uint32_t node_id, |
| std::unique_ptr<TransformPair> content_transform, |
| mojo::RectFPtr content_clip, |
| mojo::gfx::composition::HitTestBehaviorPtr hit_test_behavior, |
| Combinator combinator, |
| const std::vector<uint32_t>& child_node_ids) |
| : node_id_(node_id), |
| content_transform_(std::move(content_transform)), |
| content_clip_(content_clip.Pass()), |
| hit_test_behavior_(hit_test_behavior.Pass()), |
| combinator_(combinator), |
| child_node_ids_(child_node_ids) {} |
| |
| Node::~Node() {} |
| |
| std::string Node::FormattedLabel(const SceneContent* content) const { |
| return content->FormattedLabelForNode(node_id_); |
| } |
| |
| bool Node::RecordContent(SceneContentBuilder* builder) const { |
| DCHECK(builder); |
| |
| for (const auto& child_node_id : child_node_ids_) { |
| if (!builder->RequireNode(child_node_id, node_id_)) |
| return false; |
| } |
| return true; |
| } |
| |
| Snapshot::Disposition Node::RecordSnapshot(const SceneContent* content, |
| SnapshotBuilder* builder) const { |
| DCHECK(content); |
| DCHECK(builder); |
| |
| switch (combinator_) { |
| // MERGE: All or nothing. |
| case Combinator::MERGE: { |
| for (uint32_t child_node_id : child_node_ids_) { |
| const Node* child_node = content->GetNode(child_node_id); |
| DCHECK(child_node); |
| Snapshot::Disposition disposition = |
| builder->SnapshotNode(child_node, content); |
| if (disposition == Snapshot::Disposition::kCycle) |
| return disposition; |
| if (disposition == Snapshot::Disposition::kBlocked) { |
| if (builder->block_log()) { |
| *builder->block_log() |
| << "Node with MERGE combinator blocked since " |
| "one of its children is blocked: " |
| << FormattedLabel(content) << ", blocked child " |
| << child_node->FormattedLabel(content) << std::endl; |
| } |
| return disposition; |
| } |
| } |
| return Snapshot::Disposition::kSuccess; |
| } |
| |
| // PRUNE: Silently discard blocked children. |
| case Combinator::PRUNE: { |
| for (uint32_t child_node_id : child_node_ids_) { |
| const Node* child_node = content->GetNode(child_node_id); |
| DCHECK(child_node); |
| Snapshot::Disposition disposition = |
| builder->SnapshotNode(child_node, content); |
| if (disposition == Snapshot::Disposition::kCycle) |
| return disposition; |
| } |
| return Snapshot::Disposition::kSuccess; |
| } |
| |
| // FALLBACK: Keep only the first unblocked child. |
| case Combinator::FALLBACK: { |
| if (child_node_ids_.empty()) |
| return Snapshot::Disposition::kSuccess; |
| for (uint32_t child_node_id : child_node_ids_) { |
| const Node* child_node = content->GetNode(child_node_id); |
| DCHECK(child_node); |
| Snapshot::Disposition disposition = |
| builder->SnapshotNode(child_node, content); |
| if (disposition != Snapshot::Disposition::kBlocked) |
| return disposition; |
| } |
| if (builder->block_log()) { |
| *builder->block_log() << "Node with FALLBACK combinator blocked since " |
| "all of its children are blocked: " |
| << FormattedLabel(content) << std::endl; |
| } |
| return Snapshot::Disposition::kBlocked; |
| } |
| |
| default: { |
| if (builder->block_log()) { |
| *builder->block_log() |
| << "Unrecognized combinator: " << FormattedLabel(content) |
| << std::endl; |
| } |
| return Snapshot::Disposition::kBlocked; |
| } |
| } |
| } |
| |
| template <typename Func> |
| void Node::TraverseSnapshottedChildren(const SceneContent* content, |
| const Snapshot* snapshot, |
| const Func& func) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| |
| switch (combinator_) { |
| // MERGE: All or nothing. |
| case Combinator::MERGE: { |
| for (uint32_t child_node_id : child_node_ids_) { |
| const Node* child_node = content->GetNode(child_node_id); |
| DCHECK(child_node); |
| DCHECK(!snapshot->IsNodeBlocked(child_node)); |
| if (!func(child_node)) |
| return; |
| } |
| return; |
| } |
| |
| // PRUNE: Silently discard blocked children. |
| case Combinator::PRUNE: { |
| for (uint32_t child_node_id : child_node_ids_) { |
| const Node* child_node = content->GetNode(child_node_id); |
| DCHECK(child_node); |
| if (!snapshot->IsNodeBlocked(child_node) && !func(child_node)) |
| return; |
| } |
| return; |
| } |
| |
| // FALLBACK: Keep only the first unblocked child. |
| case Combinator::FALLBACK: { |
| if (child_node_ids_.empty()) |
| return; |
| for (uint32_t child_node_id : child_node_ids_) { |
| const Node* child_node = content->GetNode(child_node_id); |
| DCHECK(child_node); |
| if (!snapshot->IsNodeBlocked(child_node)) { |
| func(child_node); // don't care about the result because we |
| return; // always stop after the first one |
| } |
| } |
| NOTREACHED(); |
| return; |
| } |
| |
| default: { |
| NOTREACHED(); |
| return; |
| } |
| } |
| } |
| |
| void Node::Paint(const SceneContent* content, |
| const Snapshot* snapshot, |
| SkCanvas* canvas) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(canvas); |
| |
| const bool must_save = content_transform_ || content_clip_; |
| if (must_save) { |
| canvas->save(); |
| if (content_transform_) |
| canvas->concat(content_transform_->forward()); |
| if (content_clip_) |
| canvas->clipRect(content_clip_->To<SkRect>()); |
| } |
| |
| PaintInner(content, snapshot, canvas); |
| |
| if (must_save) |
| canvas->restore(); |
| } |
| |
| void Node::PaintInner(const SceneContent* content, |
| const Snapshot* snapshot, |
| SkCanvas* canvas) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(canvas); |
| |
| TraverseSnapshottedChildren( |
| content, snapshot, |
| [this, content, snapshot, canvas](const Node* child_node) -> bool { |
| child_node->Paint(content, snapshot, canvas); |
| return true; |
| }); |
| } |
| |
| bool Node::HitTest(const SceneContent* content, |
| const Snapshot* snapshot, |
| const SkPoint& parent_point, |
| const SkMatrix44& global_to_parent_transform, |
| mojo::Array<mojo::gfx::composition::HitPtr>* hits) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(hits); |
| |
| // TODO(jeffbrown): These calculations should probably be happening using |
| // a 4x4 matrix instead. |
| SkPoint local_point(parent_point); |
| SkMatrix global_to_local_transform(global_to_parent_transform); |
| if (content_transform_) { |
| // TODO(jeffbrown): Defer matrix multiplications using a matrix stack. |
| local_point = content_transform_->InverseMapPoint(parent_point); |
| global_to_local_transform.preConcat(content_transform_->GetInverse()); |
| } |
| |
| if (content_clip_ && !Contains(content_clip_->To<SkRect>(), local_point)) |
| return false; |
| |
| bool opaque_children = false; |
| if (!hit_test_behavior_ || !hit_test_behavior_->prune) { |
| opaque_children = HitTestInner(content, snapshot, local_point, |
| global_to_local_transform, hits); |
| } |
| |
| return HitTestSelf(content, snapshot, local_point, global_to_local_transform, |
| hits) || |
| opaque_children; |
| } |
| |
| bool Node::HitTestInner( |
| const SceneContent* content, |
| const Snapshot* snapshot, |
| const SkPoint& local_point, |
| const SkMatrix44& global_to_local_transform, |
| mojo::Array<mojo::gfx::composition::HitPtr>* hits) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(hits); |
| |
| // TODO(jeffbrown): Implement a more efficient way to traverse children in |
| // reverse order. |
| std::vector<const Node*> children; |
| TraverseSnapshottedChildren( |
| content, snapshot, [this, &children](const Node* child_node) -> bool { |
| children.push_back(child_node); |
| return true; |
| }); |
| |
| for (auto it = children.crbegin(); it != children.crend(); ++it) { |
| if ((*it)->HitTest(content, snapshot, local_point, |
| global_to_local_transform, hits)) |
| return true; // opaque child covering siblings |
| } |
| return false; |
| } |
| |
| bool Node::HitTestSelf( |
| const SceneContent* content, |
| const Snapshot* snapshot, |
| const SkPoint& local_point, |
| const SkMatrix44& global_to_local_transform, |
| mojo::Array<mojo::gfx::composition::HitPtr>* hits) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(hits); |
| |
| if (!hit_test_behavior_ || |
| hit_test_behavior_->visibility == |
| mojo::gfx::composition::HitTestBehavior::Visibility::INVISIBLE) |
| return false; |
| |
| if (hit_test_behavior_->hit_rect && |
| !Contains(hit_test_behavior_->hit_rect->To<SkRect>(), local_point)) |
| return false; |
| |
| auto hit = mojo::gfx::composition::Hit::New(); |
| hit->set_node(mojo::gfx::composition::NodeHit::New()); |
| hit->get_node()->node_id = node_id_; |
| hit->get_node()->transform = |
| mojo::ConvertTo<mojo::TransformPtr>(global_to_local_transform); |
| hits->push_back(hit.Pass()); |
| return hit_test_behavior_->visibility == |
| mojo::gfx::composition::HitTestBehavior::Visibility::OPAQUE; |
| } |
| |
| RectNode::RectNode(uint32_t node_id, |
| std::unique_ptr<TransformPair> content_transform, |
| mojo::RectFPtr content_clip, |
| mojo::gfx::composition::HitTestBehaviorPtr hit_test_behavior, |
| Combinator combinator, |
| const std::vector<uint32_t>& child_node_ids, |
| const mojo::RectF& content_rect, |
| const mojo::gfx::composition::Color& color) |
| : Node(node_id, |
| std::move(content_transform), |
| content_clip.Pass(), |
| hit_test_behavior.Pass(), |
| combinator, |
| child_node_ids), |
| content_rect_(content_rect), |
| color_(color) {} |
| |
| RectNode::~RectNode() {} |
| |
| void RectNode::PaintInner(const SceneContent* content, |
| const Snapshot* snapshot, |
| SkCanvas* canvas) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(canvas); |
| |
| SkPaint paint; |
| paint.setColor(MakeSkColor(color_)); |
| canvas->drawRect(content_rect_.To<SkRect>(), paint); |
| |
| Node::PaintInner(content, snapshot, canvas); |
| } |
| |
| ImageNode::ImageNode( |
| uint32_t node_id, |
| std::unique_ptr<TransformPair> content_transform, |
| mojo::RectFPtr content_clip, |
| mojo::gfx::composition::HitTestBehaviorPtr hit_test_behavior, |
| Combinator combinator, |
| const std::vector<uint32_t>& child_node_ids, |
| const mojo::RectF& content_rect, |
| mojo::RectFPtr image_rect, |
| uint32 image_resource_id, |
| mojo::gfx::composition::BlendPtr blend) |
| : Node(node_id, |
| std::move(content_transform), |
| content_clip.Pass(), |
| hit_test_behavior.Pass(), |
| combinator, |
| child_node_ids), |
| content_rect_(content_rect), |
| image_rect_(image_rect.Pass()), |
| image_resource_id_(image_resource_id), |
| blend_(blend.Pass()) {} |
| |
| ImageNode::~ImageNode() {} |
| |
| bool ImageNode::RecordContent(SceneContentBuilder* builder) const { |
| DCHECK(builder); |
| |
| return Node::RecordContent(builder) && |
| builder->RequireResource(image_resource_id_, Resource::Type::kImage, |
| node_id()); |
| } |
| |
| void ImageNode::PaintInner(const SceneContent* content, |
| const Snapshot* snapshot, |
| SkCanvas* canvas) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(canvas); |
| |
| auto image_resource = static_cast<const ImageResource*>( |
| content->GetResource(image_resource_id_, Resource::Type::kImage)); |
| DCHECK(image_resource); |
| |
| SkPaint paint; |
| SetPaintForBlend(&paint, blend_.get()); |
| |
| canvas->drawImageRect(image_resource->image()->image().get(), |
| image_rect_ |
| ? image_rect_->To<SkRect>() |
| : SkRect::MakeWH(image_resource->image()->width(), |
| image_resource->image()->height()), |
| content_rect_.To<SkRect>(), &paint); |
| |
| Node::PaintInner(content, snapshot, canvas); |
| } |
| |
| SceneNode::SceneNode( |
| uint32_t node_id, |
| std::unique_ptr<TransformPair> content_transform, |
| mojo::RectFPtr content_clip, |
| mojo::gfx::composition::HitTestBehaviorPtr hit_test_behavior, |
| Combinator combinator, |
| const std::vector<uint32_t>& child_node_ids, |
| uint32_t scene_resource_id, |
| uint32_t scene_version) |
| : Node(node_id, |
| std::move(content_transform), |
| content_clip.Pass(), |
| hit_test_behavior.Pass(), |
| combinator, |
| child_node_ids), |
| scene_resource_id_(scene_resource_id), |
| scene_version_(scene_version) {} |
| |
| SceneNode::~SceneNode() {} |
| |
| bool SceneNode::RecordContent(SceneContentBuilder* builder) const { |
| DCHECK(builder); |
| |
| return Node::RecordContent(builder) && |
| builder->RequireResource(scene_resource_id_, Resource::Type::kScene, |
| node_id()); |
| } |
| |
| Snapshot::Disposition SceneNode::RecordSnapshot( |
| const SceneContent* content, |
| SnapshotBuilder* builder) const { |
| DCHECK(content); |
| DCHECK(builder); |
| |
| Snapshot::Disposition disposition = |
| builder->SnapshotReferencedScene(this, content); |
| if (disposition != Snapshot::Disposition::kSuccess) |
| return disposition; |
| return Node::RecordSnapshot(content, builder); |
| } |
| |
| void SceneNode::PaintInner(const SceneContent* content, |
| const Snapshot* snapshot, |
| SkCanvas* canvas) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(canvas); |
| |
| const SceneContent* resolved_content = |
| snapshot->GetResolvedSceneContent(this); |
| DCHECK(resolved_content); |
| resolved_content->Paint(snapshot, canvas); |
| |
| Node::PaintInner(content, snapshot, canvas); |
| } |
| |
| bool SceneNode::HitTestInner( |
| const SceneContent* content, |
| const Snapshot* snapshot, |
| const SkPoint& local_point, |
| const SkMatrix44& global_to_local_transform, |
| mojo::Array<mojo::gfx::composition::HitPtr>* hits) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(hits); |
| |
| if (Node::HitTestInner(content, snapshot, local_point, |
| global_to_local_transform, hits)) |
| return true; // opaque child covering referenced scene |
| |
| const SceneContent* resolved_content = |
| snapshot->GetResolvedSceneContent(this); |
| DCHECK(resolved_content); |
| |
| mojo::gfx::composition::SceneHitPtr scene_hit; |
| bool opaque = resolved_content->HitTest( |
| snapshot, local_point, global_to_local_transform, &scene_hit); |
| if (scene_hit) { |
| auto hit = mojo::gfx::composition::Hit::New(); |
| hit->set_scene(scene_hit.Pass()); |
| hits->push_back(hit.Pass()); |
| } |
| return opaque; |
| } |
| |
| LayerNode::LayerNode( |
| uint32_t node_id, |
| std::unique_ptr<TransformPair> content_transform, |
| mojo::RectFPtr content_clip, |
| mojo::gfx::composition::HitTestBehaviorPtr hit_test_behavior, |
| Combinator combinator, |
| const std::vector<uint32_t>& child_node_ids, |
| const mojo::RectF& layer_rect, |
| mojo::gfx::composition::BlendPtr blend) |
| : Node(node_id, |
| std::move(content_transform), |
| content_clip.Pass(), |
| hit_test_behavior.Pass(), |
| combinator, |
| child_node_ids), |
| layer_rect_(layer_rect), |
| blend_(blend.Pass()) {} |
| |
| LayerNode::~LayerNode() {} |
| |
| void LayerNode::PaintInner(const SceneContent* content, |
| const Snapshot* snapshot, |
| SkCanvas* canvas) const { |
| DCHECK(content); |
| DCHECK(snapshot); |
| DCHECK(canvas); |
| |
| SkPaint paint; |
| SetPaintForBlend(&paint, blend_.get()); |
| |
| canvas->saveLayer(layer_rect_.To<SkRect>(), &paint); |
| Node::PaintInner(content, snapshot, canvas); |
| canvas->restore(); |
| } |
| |
| } // namespace compositor |