Commit 57cb4a50 authored by gklassen's avatar gklassen Committed by Commit Bot

Implement HitTestAggregator

Implements a HitTestAggregator component to facilitate cross process
hit testing with minimal process hops.

The HitTestAggregator maintains a list of HitTestData objects that will
be created and sent with each CompositorFrame.  These are aggregated
into a single DisplayHitTest data structure that can be made available
in shared memory to facilitate hit testing with minimal process hops
for any Display.

HitTestAggregator listens as surfaces are added to the current
CompositorFrame to ensure that the list only includes information that
matches what has been rendered in the current frame.

Aggregation occurs on the BeginFrame notification.

This change includes the implementaiton of the HitTestAggregator and
unit test cases to verify the intended behaviour.

Next steps will include completion of the creation and transport of
HitTestData objects, implementation of shared memory and using this
component to target incoming events.

BUG=732399

Review-Url: https://codereview.chromium.org/2938953002
Cr-Commit-Position: refs/heads/master@{#486852}
parent 1db86a05
...@@ -13,6 +13,7 @@ viz_source_set("common") { ...@@ -13,6 +13,7 @@ viz_source_set("common") {
"gl_helper_readback_support.h", "gl_helper_readback_support.h",
"gl_helper_scaling.cc", "gl_helper_scaling.cc",
"gl_helper_scaling.h", "gl_helper_scaling.h",
"hit_test/aggregated_hit_test_region.h",
"quads/resource_format.h", "quads/resource_format.h",
"quads/shared_bitmap.cc", "quads/shared_bitmap.cc",
"quads/shared_bitmap.h", "quads/shared_bitmap.h",
...@@ -51,6 +52,7 @@ viz_source_set("common") { ...@@ -51,6 +52,7 @@ viz_source_set("common") {
"//mojo/public/cpp/bindings", "//mojo/public/cpp/bindings",
"//skia", "//skia",
"//ui/gfx:color_space", "//ui/gfx:color_space",
"//ui/gfx:geometry_skia",
"//ui/gfx/geometry", "//ui/gfx/geometry",
] ]
......
include_rules = [
"+ui/gfx/geometry",
"+ui/gfx/transform.h",
]
\ No newline at end of file
// Copyright 2017 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.
#ifndef COMPONENTS_VIZ_COMMON_HIT_TEST_AGGREGATED_HIT_TEST_REGION_H_
#define COMPONENTS_VIZ_COMMON_HIT_TEST_AGGREGATED_HIT_TEST_REGION_H_
#include <stdint.h>
#include "components/viz/common/surfaces/surface_id.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/transform.h"
namespace viz {
// A AggregatedHitTestRegion element with child_count of kEndOfList indicates
// the last element and end of the list.
constexpr int kEndOfList = -1;
// An array of AggregatedHitTestRegion elements is used to define the
// aggregated hit-test data for the Display.
//
// It is designed to be in shared memory so that the viz service can
// write the hit_test data, and the viz host can read without
// process hops.
struct AggregatedHitTestRegion {
// The FrameSinkId corresponding to this region. Events that match
// are routed to this surface.
FrameSinkId frame_sink_id;
// Flags to indicate the type of region as defined for
// mojom::HitTestRegion
uint32_t flags;
// The rectangle that defines the region in parent region's coordinate space.
gfx::Rect rect;
// The transform applied to the rect in parent region's coordinate space.
gfx::Transform transform;
// The number of children including their children below this entry.
// If this element is not matched then child_count elements can be skipped
// to move to the next entry.
int child_count;
};
} // namespace viz
#endif // COMPONENTS_VIZ_COMMON_HIT_TEST_AGGREGATED_HIT_TEST_REGION_H_
...@@ -51,6 +51,8 @@ viz_component("service") { ...@@ -51,6 +51,8 @@ viz_component("service") {
"frame_sinks/gpu_compositor_frame_sink_delegate.h", "frame_sinks/gpu_compositor_frame_sink_delegate.h",
"frame_sinks/gpu_root_compositor_frame_sink.cc", "frame_sinks/gpu_root_compositor_frame_sink.cc",
"frame_sinks/gpu_root_compositor_frame_sink.h", "frame_sinks/gpu_root_compositor_frame_sink.h",
"hit_test/hit_test_aggregator.cc",
"hit_test/hit_test_aggregator.h",
"viz_service_export.h", "viz_service_export.h",
] ]
...@@ -77,6 +79,7 @@ viz_component("service") { ...@@ -77,6 +79,7 @@ viz_component("service") {
"//cc/surfaces", "//cc/surfaces",
"//gpu/command_buffer/client:gles2_interface", "//gpu/command_buffer/client:gles2_interface",
"//gpu/ipc:command_buffer", "//gpu/ipc:command_buffer",
"//services/viz/hit_test/public/interfaces",
"//ui/gfx", "//ui/gfx",
"//ui/gfx/geometry", "//ui/gfx/geometry",
"//ui/latency", "//ui/latency",
...@@ -127,6 +130,7 @@ viz_source_set("unit_tests") { ...@@ -127,6 +130,7 @@ viz_source_set("unit_tests") {
"frame_sinks/compositor_frame_sink_support_unittest.cc", "frame_sinks/compositor_frame_sink_support_unittest.cc",
"frame_sinks/direct_layer_tree_frame_sink_unittest.cc", "frame_sinks/direct_layer_tree_frame_sink_unittest.cc",
"frame_sinks/frame_sink_manager_unittest.cc", "frame_sinks/frame_sink_manager_unittest.cc",
"hit_test/hit_test_aggregator_unittest.cc",
] ]
if (!use_aura && !is_mac) { if (!use_aura && !is_mac) {
...@@ -144,6 +148,12 @@ viz_source_set("unit_tests") { ...@@ -144,6 +148,12 @@ viz_source_set("unit_tests") {
"//base/test:test_support", "//base/test:test_support",
"//cc:test_support", "//cc:test_support",
"//components/viz/common", "//components/viz/common",
"//gpu/command_buffer/client",
"//gpu/command_buffer/client:gles2_implementation",
"//gpu/ipc:gl_in_process_context",
"//media",
"//services/viz/hit_test/public/interfaces",
"//skia",
"//testing/gmock", "//testing/gmock",
"//testing/gtest", "//testing/gtest",
"//ui/display/types", "//ui/display/types",
......
include_rules = [
"+components/viz/common",
"+cc/surfaces/surface_observer.h",
"+services/viz/hit_test/public/interfaces",
]
rjkroege@chromium.org
\ No newline at end of file
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/hit_test/hit_test_aggregator.h"
#include "components/viz/common/hit_test/aggregated_hit_test_region.h"
namespace viz {
namespace {
// TODO(gklassen): Review and select appropriate sizes based on
// telemetry / UMA.
constexpr int kInitialSize = 1024;
constexpr int kIncrementalSize = 1024;
constexpr int kMaxRegionsPerSurface = 1024;
constexpr int kMaxSize = 100 * 1024;
bool ValidateHitTestRegion(const mojom::HitTestRegionPtr& hit_test_region) {
if (hit_test_region->flags == mojom::kHitTestChildSurface) {
if (!hit_test_region->surface_id.is_valid())
return false;
}
return true;
}
bool ValidateHitTestRegionList(
const mojom::HitTestRegionListPtr& hit_test_region_list) {
if (hit_test_region_list->regions.size() > kMaxRegionsPerSurface)
return false;
for (auto& region : hit_test_region_list->regions) {
if (!ValidateHitTestRegion(region))
return false;
}
return true;
}
} // namespace
HitTestAggregator::HitTestAggregator() : weak_ptr_factory_(this) {
AllocateHitTestRegionArray();
}
HitTestAggregator::~HitTestAggregator() = default;
void HitTestAggregator::SubmitHitTestRegionList(
mojom::HitTestRegionListPtr hit_test_region_list) {
DCHECK(ValidateHitTestRegionList(hit_test_region_list));
// TODO(gklassen): Runtime validation that hit_test_region_list is valid.
// TODO(gklassen): Inform FrameSink that the hit_test_region_list is invalid.
// TODO(gklassen): FrameSink needs to inform the host of a difficult renderer.
pending_[hit_test_region_list->surface_id] = std::move(hit_test_region_list);
}
bool HitTestAggregator::OnSurfaceDamaged(const SurfaceId& surface_id,
const cc::BeginFrameAck& ack) {
return false;
}
void HitTestAggregator::OnSurfaceDiscarded(const SurfaceId& surface_id) {
// Update the region count.
auto active_search = active_.find(surface_id);
if (active_search != active_.end()) {
mojom::HitTestRegionList* old_hit_test_data = active_search->second.get();
active_region_count_ -= old_hit_test_data->regions.size();
}
DCHECK_GE(active_region_count_, 0);
pending_.erase(surface_id);
active_.erase(surface_id);
}
void HitTestAggregator::OnSurfaceWillDraw(const SurfaceId& surface_id) {
auto pending_search = pending_.find(surface_id);
if (pending_search == pending_.end()) {
// Have already activated pending hit_test_region_list objects for this
// surface.
return;
}
mojom::HitTestRegionList* hit_test_region_list = pending_search->second.get();
// Update the region count.
auto active_search = active_.find(surface_id);
if (active_search != active_.end()) {
mojom::HitTestRegionList* old_hit_test_data = active_search->second.get();
active_region_count_ -= old_hit_test_data->regions.size();
}
active_region_count_ += hit_test_region_list->regions.size();
DCHECK_GE(active_region_count_, 0);
active_[surface_id] = std::move(pending_[surface_id]);
pending_.erase(surface_id);
}
void HitTestAggregator::AllocateHitTestRegionArray() {
AllocateHitTestRegionArray(kInitialSize);
Swap();
AllocateHitTestRegionArray(kInitialSize);
}
void HitTestAggregator::AllocateHitTestRegionArray(int size) {
size_t num_bytes = size * sizeof(AggregatedHitTestRegion);
write_handle_ = mojo::SharedBufferHandle::Create(num_bytes);
write_size_ = size;
write_buffer_ = write_handle_->Map(num_bytes);
AggregatedHitTestRegion* region =
(AggregatedHitTestRegion*)write_buffer_.get();
region[0].child_count = kEndOfList;
}
void HitTestAggregator::PostTaskAggregate(SurfaceId display_surface_id) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&HitTestAggregator::Aggregate,
weak_ptr_factory_.GetWeakPtr(), display_surface_id));
}
void HitTestAggregator::Aggregate(SurfaceId display_surface_id) {
// Check to ensure that enough memory has been allocated.
int size = write_size_;
int max_size = active_region_count_ + active_.size() + 1;
if (max_size > kMaxSize)
max_size = kMaxSize;
if (max_size > size) {
size = (1 + max_size / kIncrementalSize) * kIncrementalSize;
AllocateHitTestRegionArray(size);
}
AppendRoot(display_surface_id);
}
void HitTestAggregator::AppendRoot(SurfaceId surface_id) {
auto search = active_.find(surface_id);
DCHECK(search != active_.end());
mojom::HitTestRegionList* hit_test_region_list = search->second.get();
AggregatedHitTestRegion* regions =
static_cast<AggregatedHitTestRegion*>(write_buffer_.get());
regions[0].frame_sink_id = hit_test_region_list->surface_id.frame_sink_id();
regions[0].flags = hit_test_region_list->flags;
regions[0].rect = hit_test_region_list->bounds;
regions[0].transform = hit_test_region_list->transform;
int region_index = 1;
for (const auto& region : hit_test_region_list->regions) {
if (region_index >= write_size_ - 1)
break;
region_index = AppendRegion(regions, region_index, region);
}
DCHECK_GE(region_index, 1);
regions[0].child_count = region_index - 1;
regions[region_index].child_count = kEndOfList;
}
int HitTestAggregator::AppendRegion(AggregatedHitTestRegion* regions,
int region_index,
const mojom::HitTestRegionPtr& region) {
AggregatedHitTestRegion* element = &regions[region_index];
element->frame_sink_id = region->surface_id.frame_sink_id();
element->flags = region->flags;
element->rect = region->rect;
element->transform = region->transform;
int parent_index = region_index++;
if (region_index >= write_size_ - 1) {
element->child_count = 0;
return region_index;
}
if (region->flags == mojom::kHitTestChildSurface) {
auto search = active_.find(region->surface_id);
if (search == active_.end()) {
// Surface HitTestRegionList not found - it may be late.
// Don't include this region so that it doesn't receive events.
return parent_index;
}
// Rather than add a node in the tree for this hit_test_region_list element
// we can simplify the tree by merging the flags and transform into
// the kHitTestChildSurface element.
mojom::HitTestRegionList* hit_test_region_list = search->second.get();
if (!hit_test_region_list->transform.IsIdentity())
element->transform.PreconcatTransform(hit_test_region_list->transform);
element->flags |= hit_test_region_list->flags;
for (const auto& child_region : hit_test_region_list->regions) {
region_index = AppendRegion(regions, region_index, child_region);
if (region_index >= write_size_ - 1)
break;
}
}
DCHECK_GE(region_index - parent_index - 1, 0);
element->child_count = region_index - parent_index - 1;
return region_index;
}
void HitTestAggregator::Swap() {
using std::swap;
swap(read_handle_, write_handle_);
swap(read_size_, write_size_);
swap(read_buffer_, write_buffer_);
}
} // namespace viz
// Copyright 2017 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.
#ifndef COMPONENTS_VIZ_SERVICE_HIT_TEST_HIT_TEST_AGGREGATOR_H_
#define COMPONENTS_VIZ_SERVICE_HIT_TEST_HIT_TEST_AGGREGATOR_H_
#include "cc/surfaces/surface_observer.h"
#include "components/viz/common/hit_test/aggregated_hit_test_region.h"
#include "components/viz/common/surfaces/surface_id.h"
#include "components/viz/service/viz_service_export.h"
#include "services/viz/hit_test/public/interfaces/hit_test_region_list.mojom.h"
namespace viz {
// HitTestAggregator collects HitTestRegionList objects from surfaces and
// aggregates them into a DisplayHitTesData structue made available in
// shared memory to enable efficient hit testing across processes.
//
// This is intended to be created in the viz or GPU process. For mus+ash this
// will be true after the mus process split.
class VIZ_SERVICE_EXPORT HitTestAggregator : public cc::SurfaceObserver {
public:
HitTestAggregator();
~HitTestAggregator();
// Called when HitTestRegionList is submitted along with every call
// to SubmitCompositorFrame. This is collected in pending_ until
// surfaces are aggregated and put on the display.
void SubmitHitTestRegionList(
mojom::HitTestRegionListPtr hit_test_region_list);
// Called after surfaces have been aggregated into the DisplayFrame.
// In this call HitTestRegionList structures received from active surfaces
// are aggregated into the HitTestRegionList structure in
// shared memory used for event targetting.
void Aggregate(SurfaceId display_surface_id);
// Performs the work of Aggregate by creating a PostTask so that
// the work is not directly on the call.
void PostTaskAggregate(SurfaceId display_surface_id);
// Called at BeginFrame. Swaps buffers in shared memory.
void Swap();
protected:
// cc::SurfaceObserver:
void OnSurfaceCreated(const SurfaceInfo& surface_info) override {}
void OnSurfaceDestroyed(const SurfaceId& surface_id) override {}
bool OnSurfaceDamaged(const SurfaceId& surface_id,
const cc::BeginFrameAck& ack) override;
void OnSurfaceDiscarded(const SurfaceId& surface_id) override;
void OnSurfaceDamageExpected(const SurfaceId& surface_id,
const cc::BeginFrameArgs& args) override {}
// Called when a surface has been aggregated and added to the
// display frame. HitTestRegionList objects are held but ignored until
// this happens. HitTestRegionList for the surface is copied from |pending_|
// to |active_| in this method.
void OnSurfaceWillDraw(const SurfaceId& surface_id) override;
// The collection of received HitTestRegionList objects that have not yet
// been added to the DisplayFrame (OnSurfaceWillDraw has not been called).
std::map<SurfaceId, mojom::HitTestRegionListPtr> pending_;
// The collection of HitTestRegionList objects that have been added to the
// DisplayFrame (OnSurfaceWillDraw has been called).
std::map<SurfaceId, mojom::HitTestRegionListPtr> active_;
// Keeps track of the number of regions in the active list
// so that we know when we exceed the available length.
int active_region_count_ = 0;
mojo::ScopedSharedBufferHandle read_handle_;
mojo::ScopedSharedBufferHandle write_handle_;
// The number of elements allocated.
int read_size_ = 0;
int write_size_ = 0;
mojo::ScopedSharedBufferMapping read_buffer_;
mojo::ScopedSharedBufferMapping write_buffer_;
private:
// Allocates memory for the AggregatedHitTestRegion array.
void AllocateHitTestRegionArray();
void AllocateHitTestRegionArray(int length);
// Appends the root element to the AggregatedHitTestRegion array.
void AppendRoot(SurfaceId surface_id);
// Appends a region to the HitTestRegionList structure to recursively
// build the tree.
int AppendRegion(AggregatedHitTestRegion* regions,
int region_index,
const mojom::HitTestRegionPtr& region);
// Handles the case when this object is deleted after
// the PostTaskAggregation call is scheduled but before invocation.
base::WeakPtrFactory<HitTestAggregator> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(HitTestAggregator);
};
} // namespace viz
#endif // COMPONENTS_VIZ_SERVICE_HIT_TEST_HIT_TEST_AGGREGATOR_H_
# Copyright 2017 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.
import("//mojo/public/tools/bindings/mojom.gni")
mojom("interfaces") {
sources = [
"hit_test_region_list.mojom",
]
public_deps = [
"//cc/ipc:interfaces",
"//ui/gfx/geometry/mojo",
"//ui/gfx/mojo",
]
}
rjkroege@chromium.org
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
// Copyright 2017 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.
module viz.mojom;
import "cc/ipc/surface_id.mojom";
import "ui/gfx/geometry/mojo/geometry.mojom";
import "ui/gfx/mojo/transform.mojom";
// Region maps to this surface (me).
const uint32 kHitTestMine = 0x01;
// Region ignored for hit testing (transparent backgrounds & hover:none).
const uint32 kHitTestIgnore = 0x02;
// Region maps to child surface (OOPIF).
const uint32 kHitTestChildSurface = 0x04;
// Irregular boundary - send HitTestRequest to resolve.
const uint32 kHitTestAsk = 0x08;
// Touch event handler exists.
const uint32 kHitTestTouchEventHandler = 0x10;
struct HitTestRegion {
// Flags to indicate the type of HitTestRegion.
uint32 flags;
// SurfaceId is required when flags = kHitTestChildSurface.
cc.mojom.SurfaceId surface_id;
// The rect of the region in the coordinate space of the embedder.
gfx.mojom.Rect rect;
// The transform of the region. The transform applied to the rect
// defines the space occupied by this region in the coordinate space of
// the embedder.
gfx.mojom.Transform transform;
};
struct HitTestRegionList {
// SurfaceId corresponding to this HitTestData.
cc.mojom.SurfaceId surface_id;
// Flags indicate how to handle events that match no sub-regions.
// kHitTestMine routes un-matched events to this surface (opaque).
// kHitTestIgnore keeps previous match in the parent (transparent).
uint32 flags;
// The bounds of the surface.
gfx.mojom.Rect bounds;
// The transform applied to all regions in this surface.
gfx.mojom.Transform transform;
// The list of sub-regions in front to back order.
array<HitTestRegion> regions;
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment