Skip to content

Commit

Permalink
Track hit test opaqueness of cc::Layers
Browse files Browse the repository at this point in the history
During paint, ObjectPainter::RecordHitTestData() records hit test data
with hit test opaqueness, which is transparent with
pointer-events:none, mixed with border radius, or opaque otherwise.

The opaqueness of multiple hit test data are then merged at the
PaintChunk level, then at PendingLayer level, then is passed to
cc::Layer then cc::LayerImpl.

The new behavior is behind the blink runtime flag "HitTestOpaqueness".
The opaqueness on cc::LayerImpl will be used in the next CL to reduce
unreliable scroll begins on the compositor.

Bug: 1413877
Change-Id: I416e3cfab4157d50db3fee91aff765cb2dd4a245
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4754732
Reviewed-by: Philip Rogers <pdr@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1182332}
  • Loading branch information
wangxianzhu authored and Chromium LUCI CQ committed Aug 10, 2023
1 parent 0281be9 commit 0e6d443
Show file tree
Hide file tree
Showing 50 changed files with 772 additions and 349 deletions.
3 changes: 3 additions & 0 deletions cc/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ cc_component("cc") {
"input/browser_controls_offset_manager_client.h",
"input/browser_controls_state.h",
"input/compositor_input_interfaces.h",
"input/hit_test_opaqueness.cc",
"input/hit_test_opaqueness.h",
"input/input_handler.cc",
"input/input_handler.h",
"input/layer_selection_bound.cc",
Expand Down Expand Up @@ -704,6 +706,7 @@ cc_test("cc_unittests") {
"benchmarks/micro_benchmark_controller_unittest.cc",
"debug/rendering_stats_unittest.cc",
"input/browser_controls_offset_manager_unittest.cc",
"input/hit_test_opaqueness_unittest.cc",
"input/main_thread_scrolling_reason_unittest.cc",
"input/scroll_snap_data_unittest.cc",
"input/scroll_state_unittest.cc",
Expand Down
59 changes: 59 additions & 0 deletions cc/input/hit_test_opaqueness.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/input/hit_test_opaqueness.h"
#include "base/check_op.h"
#include "base/notreached.h"

namespace cc {

namespace {

// Returns true if UnionRects(rect1, rect2) doesn't have any area not covered
// by either rect1 or rect2.
bool UnionIsTight(const gfx::Rect& rect1, const gfx::Rect& rect2) {
return gfx::UnionRects(rect1, rect2) == gfx::MaximumCoveredRect(rect1, rect2);
}

} // anonymous namespace

const char* HitTestOpaquenessToString(HitTestOpaqueness opaqueness) {
return opaqueness == HitTestOpaqueness::kTransparent ? "transparent"
: opaqueness == HitTestOpaqueness::kOpaque ? "opaque"
: "mixed";
}

HitTestOpaqueness UnionHitTestOpaqueness(const gfx::Rect& rect1,
HitTestOpaqueness opaqueness1,
const gfx::Rect& rect2,
HitTestOpaqueness opaqueness2) {
if (rect1.IsEmpty()) {
return opaqueness2;
}
if (rect2.IsEmpty()) {
return opaqueness1;
}
if (opaqueness1 < opaqueness2) {
return UnionHitTestOpaqueness(rect2, opaqueness2, rect1, opaqueness1);
}

switch (opaqueness1) {
case HitTestOpaqueness::kTransparent:
DCHECK_EQ(opaqueness2, HitTestOpaqueness::kTransparent);
return HitTestOpaqueness::kTransparent;
case HitTestOpaqueness::kMixed:
DCHECK_NE(opaqueness2, HitTestOpaqueness::kOpaque);
return HitTestOpaqueness::kMixed;
case HitTestOpaqueness::kOpaque:
if (opaqueness2 == HitTestOpaqueness::kOpaque) {
return UnionIsTight(rect1, rect2) ? HitTestOpaqueness::kOpaque
: HitTestOpaqueness::kMixed;
}
return rect1.Contains(rect2) ? HitTestOpaqueness::kOpaque
: HitTestOpaqueness::kMixed;
}
NOTREACHED_NORETURN();
}

} // namespace cc
38 changes: 38 additions & 0 deletions cc/input/hit_test_opaqueness.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CC_INPUT_HIT_TEST_OPAQUENESS_H_
#define CC_INPUT_HIT_TEST_OPAQUENESS_H_

#include <stdint.h>

#include "cc/cc_export.h"
#include "ui/gfx/geometry/rect.h"

namespace cc {

// Describes the opaqueness for hit testing in a layered content e.g. a layer.
enum class HitTestOpaqueness : uint8_t {
// The whole layered content is transparent (i.e. as if it didn't exist)
// to hit test.
kTransparent,
// Some areas may be transparent, while some may be opaque.
kMixed,
// The whole layered content is opaque to hit test.
kOpaque,
};

CC_EXPORT const char* HitTestOpaquenessToString(HitTestOpaqueness opaqueness);

// Returns the hit test opaqueness of the bounds containing `rect1` and `rect2`
// of specified opaqueness.
CC_EXPORT HitTestOpaqueness
UnionHitTestOpaqueness(const gfx::Rect& rect1,
HitTestOpaqueness opaqueness1,
const gfx::Rect& rect2,
HitTestOpaqueness opaqueness2);

} // namespace cc

#endif // CC_INPUT_HIT_TEST_OPAQUENESS_H_
111 changes: 111 additions & 0 deletions cc/input/hit_test_opaqueness_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/input/hit_test_opaqueness.h"

#include "testing/gtest/include/gtest/gtest.h"

namespace cc {
namespace {

constexpr auto kTransparent = HitTestOpaqueness::kTransparent;
constexpr auto kMixed = HitTestOpaqueness::kMixed;
constexpr auto kOpaque = HitTestOpaqueness::kOpaque;

#define EXPECT_OPAQUENESS(expected, r1, o1, r2, o2) \
do { \
EXPECT_EQ(expected, UnionHitTestOpaqueness(r1, o1, r2, o2)); \
EXPECT_EQ(expected, UnionHitTestOpaqueness(r2, o2, r1, o1)); \
} while (false)

TEST(HitTestOpaquenessTest, BothTransparent) {
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(), kTransparent, gfx::Rect(),
kTransparent);
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(0, 0, 100, 100), kTransparent);
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(0, 100, 100, 100), kTransparent);
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(0, 0, 50, 100), kTransparent);
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(0, 200, 100, 100), kTransparent);
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(50, 50, 100, 100), kTransparent);
}

TEST(HitTestOpaquenessTest, BothOpaque) {
EXPECT_OPAQUENESS(kOpaque, gfx::Rect(), kOpaque, gfx::Rect(), kOpaque);
EXPECT_OPAQUENESS(kOpaque, gfx::Rect(0, 0, 100, 100), kOpaque,
gfx::Rect(0, 0, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kOpaque, gfx::Rect(0, 0, 100, 100), kOpaque,
gfx::Rect(0, 100, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kOpaque, gfx::Rect(0, 0, 100, 100), kOpaque,
gfx::Rect(0, 0, 50, 100), kOpaque);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kOpaque,
gfx::Rect(0, 200, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kOpaque,
gfx::Rect(50, 50, 100, 100), kOpaque);
}

TEST(HitTestOpaquenessTest, BothMixed) {
EXPECT_OPAQUENESS(kMixed, gfx::Rect(), kMixed, gfx::Rect(), kMixed);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 0, 100, 100), kMixed);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 100, 100, 100), kMixed);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 0, 50, 100), kMixed);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 200, 100, 100), kMixed);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(50, 50, 100, 100), kMixed);
}

TEST(HitTestOpaquenessTest, MixedAndTransparent) {
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(), kMixed,
gfx::Rect(0, 0, 100, 100), kTransparent);
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(), kMixed,
gfx::Rect(0, 0, 100, 100), kTransparent);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 0, 100, 100), kTransparent);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 100, 100, 100), kTransparent);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 100, 100, 100), kTransparent);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(50, 50, 100, 100), kTransparent);
}

TEST(HitTestOpaquenessTest, MixedAndOpaque) {
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed, gfx::Rect(),
kOpaque);
EXPECT_OPAQUENESS(kOpaque, gfx::Rect(), kMixed, gfx::Rect(0, 0, 100, 100),
kOpaque);
EXPECT_OPAQUENESS(kOpaque, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 0, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 100, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(0, 100, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kMixed,
gfx::Rect(50, 50, 100, 100), kOpaque);
}

TEST(HitTestOpaquenessTest, TransparentAndOpaque) {
EXPECT_OPAQUENESS(kTransparent, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(), kOpaque);
EXPECT_OPAQUENESS(kOpaque, gfx::Rect(), kTransparent,
gfx::Rect(0, 0, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kOpaque, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(0, 0, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(0, 100, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(0, 100, 100, 100), kOpaque);
EXPECT_OPAQUENESS(kMixed, gfx::Rect(0, 0, 100, 100), kTransparent,
gfx::Rect(50, 50, 100, 100), kOpaque);
}

} // anonymous namespace
} // namespace cc
22 changes: 12 additions & 10 deletions cc/layers/layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ LayerDebugInfo::LayerDebugInfo(const LayerDebugInfo&) = default;
LayerDebugInfo::~LayerDebugInfo() = default;

Layer::Inputs::Inputs()
: hit_testable(false),
contents_opaque(false),
: contents_opaque(false),
contents_opaque_for_text(false),
is_drawable(false),
double_sided(true),
Expand Down Expand Up @@ -857,18 +856,20 @@ void Layer::SetBlendMode(SkBlendMode blend_mode) {
SetPropertyTreesNeedRebuild();
}

void Layer::SetHitTestable(bool should_hit_test) {
void Layer::SetHitTestOpaqueness(HitTestOpaqueness opaqueness) {
DCHECK(IsPropertyChangeAllowed());
auto& inputs = inputs_.Write(*this);
if (inputs.hit_testable == should_hit_test)
if (inputs.hit_test_opaqueness == opaqueness) {
return;
inputs.hit_testable = should_hit_test;
}
inputs.hit_test_opaqueness = opaqueness;
SetPropertyTreesNeedRebuild();
SetNeedsCommit();
}

bool Layer::HitTestable() const {
return inputs_.Read(*this).hit_testable;
void Layer::SetHitTestable(bool hit_testable) {
SetHitTestOpaqueness(hit_testable ? HitTestOpaqueness::kMixed
: HitTestOpaqueness::kTransparent);
}

void Layer::SetContentsOpaque(bool opaque) {
Expand Down Expand Up @@ -1394,14 +1395,15 @@ std::string Layer::ToString() const {
" name: %s\n"
" Bounds: %s\n"
" ElementId: %s\n"
" HitTestable: %d\n"
" HitTestOpaqueness: %s\n"
" OffsetToTransformParent: %s\n"
" clip_tree_index: %d\n"
" effect_tree_index: %d\n"
" scroll_tree_index: %d\n"
" transform_tree_index: %d\n",
id(), DebugName().c_str(), bounds().ToString().c_str(),
element_id().ToString().c_str(), HitTestable(),
element_id().ToString().c_str(),
HitTestOpaquenessToString(hit_test_opaqueness()),
offset_to_transform_parent().ToString().c_str(), clip_tree_index(),
effect_tree_index(), scroll_tree_index(), transform_tree_index());
}
Expand Down Expand Up @@ -1471,7 +1473,7 @@ void Layer::PushPropertiesTo(LayerImpl* layer,
layer->SetScrollTreeIndex(scroll_tree_index(property_trees));
layer->SetOffsetToTransformParent(offset_to_transform_parent_.Read(*this));
layer->SetDrawsContent(draws_content());
layer->SetHitTestable(HitTestable());
layer->SetHitTestOpaqueness(inputs.hit_test_opaqueness);
// subtree_property_changed_ is propagated to all descendants while building
// property trees. So, it is enough to check it only for the current layer.
if (subtree_property_changed_.Read(*this))
Expand Down
15 changes: 10 additions & 5 deletions cc/layers/layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "cc/base/region.h"
#include "cc/benchmarks/micro_benchmark.h"
#include "cc/cc_export.h"
#include "cc/input/hit_test_opaqueness.h"
#include "cc/input/scroll_snap_data.h"
#include "cc/layers/layer_collections.h"
#include "cc/layers/touch_action_region.h"
Expand Down Expand Up @@ -405,9 +406,13 @@ class CC_EXPORT Layer : public base::RefCounted<Layer>,
return inputs_.Read(*this).contents_opaque_for_text;
}

// Set or get whether this layer should be a hit test target
void SetHitTestable(bool should_hit_test);
virtual bool HitTestable() const;
void SetHitTestOpaqueness(HitTestOpaqueness opaqueness);
// For callers that don't know the HitTestOpaqueness::kOpaque concept.
void SetHitTestable(bool hit_testable);
HitTestOpaqueness hit_test_opaqueness() const {
return static_cast<HitTestOpaqueness>(
inputs_.Read(*this).hit_test_opaqueness);
}

// For layer tree mode only.
// Set or get the transform to be used when compositing this layer into its
Expand Down Expand Up @@ -1001,8 +1006,8 @@ class CC_EXPORT Layer : public base::RefCounted<Layer>,

gfx::Size bounds;

// Hit testing depends on this bit.
bool hit_testable : 1;
HitTestOpaqueness hit_test_opaqueness = HitTestOpaqueness::kTransparent;

bool contents_opaque : 1;
bool contents_opaque_for_text : 1;
bool is_drawable : 1;
Expand Down
24 changes: 15 additions & 9 deletions cc/layers/layer_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ LayerImpl::LayerImpl(LayerTreeImpl* tree_impl,
should_check_backface_visibility_(false),
draws_content_(false),
contributes_to_drawn_render_surface_(false),
hit_testable_(false),
is_inner_viewport_scroll_layer_(false),
background_color_(SkColors::kTransparent),
safe_opaque_background_color_(SkColors::kTransparent),
Expand Down Expand Up @@ -389,7 +388,7 @@ void LayerImpl::PushPropertiesTo(LayerImpl* layer) {
layer->may_contain_video_ = may_contain_video_;
layer->should_check_backface_visibility_ = should_check_backface_visibility_;
layer->draws_content_ = draws_content_;
layer->hit_testable_ = hit_testable_;
layer->hit_test_opaqueness_ = hit_test_opaqueness_;
layer->touch_action_region_ = touch_action_region_;
layer->all_touch_action_regions_ = ClonePtr(all_touch_action_regions_);
layer->background_color_ = background_color_;
Expand Down Expand Up @@ -547,26 +546,32 @@ void LayerImpl::SetDrawsContent(bool draws_content) {
NoteLayerPropertyChanged();
}

void LayerImpl::SetHitTestable(bool should_hit_test) {
if (hit_testable_ == should_hit_test)
void LayerImpl::SetHitTestOpaqueness(HitTestOpaqueness opaqueness) {
if (hit_test_opaqueness_ == opaqueness) {
return;
}

hit_testable_ = should_hit_test;
hit_test_opaqueness_ = opaqueness;
NoteLayerPropertyChanged();
}

bool LayerImpl::HitTestable() const {
EffectTree& effect_tree = GetEffectTree();
bool should_hit_test = hit_testable_;
// TODO(sunxd): remove or refactor SetHideLayerAndSubtree, or move this logic
// to subclasses of Layer. See https://crbug.com/595843 and
// https://crbug.com/931865.
// The bit |subtree_hidden| can only be true for ui::Layers. Other layers are
// not supposed to set this bit.
if (effect_tree.Node(effect_tree_index())) {
should_hit_test &= !effect_tree.Node(effect_tree_index())->subtree_hidden;
if (const EffectNode* node = effect_tree.Node(effect_tree_index())) {
if (node->subtree_hidden) {
return false;
}
}
return should_hit_test;
return hit_test_opaqueness_ != HitTestOpaqueness::kTransparent;
}

bool LayerImpl::OpaqueToHitTest() const {
return HitTestable() && hit_test_opaqueness_ == HitTestOpaqueness::kOpaque;
}

void LayerImpl::SetBackgroundColor(SkColor4f background_color) {
Expand Down Expand Up @@ -716,6 +721,7 @@ void LayerImpl::AsValueInto(base::trace_event::TracedValue* state) const {
state->EndArray();

state->SetBoolean("hit_testable", HitTestable());
state->SetBoolean("opaque_to_hit_test", OpaqueToHitTest());
state->SetBoolean("contents_opaque", contents_opaque());

if (debug_info_) {
Expand Down
Loading

0 comments on commit 0e6d443

Please sign in to comment.