summaryrefslogtreecommitdiff
path: root/chromium/components/viz/service
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/viz/service')
-rw-r--r--chromium/components/viz/service/BUILD.gn184
-rw-r--r--chromium/components/viz/service/DEPS8
-rw-r--r--chromium/components/viz/service/display/DEPS24
-rw-r--r--chromium/components/viz/service/display/README.md12
-rw-r--r--chromium/components/viz/service/display/display.cc434
-rw-r--r--chromium/components/viz/service/display/display.h135
-rw-r--r--chromium/components/viz/service/display/display_client.h24
-rw-r--r--chromium/components/viz/service/display/display_scheduler.cc492
-rw-r--r--chromium/components/viz/service/display/display_scheduler.h136
-rw-r--r--chromium/components/viz/service/display/display_scheduler_unittest.cc734
-rw-r--r--chromium/components/viz/service/display/display_unittest.cc658
-rw-r--r--chromium/components/viz/service/display/surface_aggregator.cc992
-rw-r--r--chromium/components/viz/service/display/surface_aggregator.h233
-rw-r--r--chromium/components/viz/service/display/surface_aggregator_perftest.cc187
-rw-r--r--chromium/components/viz/service/display/surface_aggregator_pixeltest.cc319
-rw-r--r--chromium/components/viz/service/display/surface_aggregator_unittest.cc3095
-rw-r--r--chromium/components/viz/service/display_embedder/DEPS32
-rw-r--r--chromium/components/viz/service/display_embedder/OWNERS6
-rw-r--r--chromium/components/viz/service/display_embedder/README.md6
-rw-r--r--chromium/components/viz/service/display_embedder/buffer_queue.cc310
-rw-r--r--chromium/components/viz/service/display_embedder/buffer_queue.h137
-rw-r--r--chromium/components/viz/service/display_embedder/buffer_queue_unittest.cc704
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator.h28
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.cc68
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.h41
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.h40
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.mm37
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.cc126
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.h55
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_win.cc39
-rw-r--r--chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_win.h35
-rw-r--r--chromium/components/viz/service/display_embedder/display_output_surface.cc152
-rw-r--r--chromium/components/viz/service/display_embedder/display_output_surface.h79
-rw-r--r--chromium/components/viz/service/display_embedder/display_output_surface_ozone.cc127
-rw-r--r--chromium/components/viz/service/display_embedder/display_output_surface_ozone.h79
-rw-r--r--chromium/components/viz/service/display_embedder/display_provider.h35
-rw-r--r--chromium/components/viz/service/display_embedder/gpu_display_provider.cc109
-rw-r--r--chromium/components/viz/service/display_embedder/gpu_display_provider.h55
-rw-r--r--chromium/components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.cc51
-rw-r--r--chromium/components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.h47
-rw-r--r--chromium/components/viz/service/display_embedder/server_shared_bitmap_manager.cc193
-rw-r--r--chromium/components/viz/service/display_embedder/server_shared_bitmap_manager.h72
-rw-r--r--chromium/components/viz/service/display_embedder/server_shared_bitmap_manager_unittest.cc169
-rw-r--r--chromium/components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.cc85
-rw-r--r--chromium/components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.h64
-rw-r--r--chromium/components/viz/service/frame_sinks/DEPS8
-rw-r--r--chromium/components/viz/service/frame_sinks/compositor_frame_sink_support.cc385
-rw-r--r--chromium/components/viz/service/frame_sinks/compositor_frame_sink_support.h173
-rw-r--r--chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_client.h53
-rw-r--r--chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_manager.h35
-rw-r--r--chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc894
-rw-r--r--chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc173
-rw-r--r--chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h102
-rw-r--r--chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink_unittest.cc177
-rw-r--r--chromium/components/viz/service/frame_sinks/frame_eviction_manager.cc179
-rw-r--r--chromium/components/viz/service/frame_sinks/frame_eviction_manager.h83
-rw-r--r--chromium/components/viz/service/frame_sinks/frame_evictor.cc56
-rw-r--r--chromium/components/viz/service/frame_sinks/frame_evictor.h46
-rw-r--r--chromium/components/viz/service/frame_sinks/frame_sink_manager_client.h27
-rw-r--r--chromium/components/viz/service/frame_sinks/frame_sink_manager_impl.cc353
-rw-r--r--chromium/components/viz/service/frame_sinks/frame_sink_manager_impl.h196
-rw-r--r--chromium/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc529
-rw-r--r--chromium/components/viz/service/frame_sinks/gpu_compositor_frame_sink.cc106
-rw-r--r--chromium/components/viz/service/frame_sinks/gpu_compositor_frame_sink.h74
-rw-r--r--chromium/components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.cc163
-rw-r--r--chromium/components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.h108
-rw-r--r--chromium/components/viz/service/frame_sinks/primary_begin_frame_source.cc89
-rw-r--r--chromium/components/viz/service/frame_sinks/primary_begin_frame_source.h58
-rw-r--r--chromium/components/viz/service/frame_sinks/referenced_surface_tracker.cc38
-rw-r--r--chromium/components/viz/service/frame_sinks/referenced_surface_tracker.h30
-rw-r--r--chromium/components/viz/service/frame_sinks/referenced_surface_tracker_unittest.cc147
-rw-r--r--chromium/components/viz/service/frame_sinks/surface_references_unittest.cc527
-rw-r--r--chromium/components/viz/service/frame_sinks/surface_resource_holder.cc70
-rw-r--r--chromium/components/viz/service/frame_sinks/surface_resource_holder.h55
-rw-r--r--chromium/components/viz/service/frame_sinks/surface_resource_holder_client.h25
-rw-r--r--chromium/components/viz/service/frame_sinks/surface_synchronization_unittest.cc1605
-rw-r--r--chromium/components/viz/service/hit_test/DEPS5
-rw-r--r--chromium/components/viz/service/hit_test/OWNERS1
-rw-r--r--chromium/components/viz/service/hit_test/hit_test_aggregator.cc213
-rw-r--r--chromium/components/viz/service/hit_test/hit_test_aggregator.h107
-rw-r--r--chromium/components/viz/service/hit_test/hit_test_aggregator_unittest.cc1017
-rw-r--r--chromium/components/viz/service/viz_service_export.h29
82 files changed, 18584 insertions, 0 deletions
diff --git a/chromium/components/viz/service/BUILD.gn b/chromium/components/viz/service/BUILD.gn
new file mode 100644
index 00000000000..22a0b3cdc14
--- /dev/null
+++ b/chromium/components/viz/service/BUILD.gn
@@ -0,0 +1,184 @@
+# 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("//build/config/ui.gni")
+import("//components/viz/viz.gni")
+
+config("viz_service_implementation") {
+}
+
+viz_component("service") {
+ sources = [
+ "display/display.cc",
+ "display/display.h",
+ "display/display_client.h",
+ "display/display_scheduler.cc",
+ "display/display_scheduler.h",
+ "display/surface_aggregator.cc",
+ "display/surface_aggregator.h",
+ "display_embedder/buffer_queue.cc",
+ "display_embedder/buffer_queue.h",
+ "display_embedder/compositor_overlay_candidate_validator.h",
+ "display_embedder/display_output_surface.cc",
+ "display_embedder/display_output_surface.h",
+ "display_embedder/display_provider.h",
+ "display_embedder/gpu_display_provider.cc",
+ "display_embedder/gpu_display_provider.h",
+ "display_embedder/in_process_gpu_memory_buffer_manager.cc",
+ "display_embedder/in_process_gpu_memory_buffer_manager.h",
+ "display_embedder/server_shared_bitmap_manager.cc",
+ "display_embedder/server_shared_bitmap_manager.h",
+ "display_embedder/shared_bitmap_allocation_notifier_impl.cc",
+ "display_embedder/shared_bitmap_allocation_notifier_impl.h",
+ "frame_sinks/compositor_frame_sink_support.cc",
+ "frame_sinks/compositor_frame_sink_support.h",
+ "frame_sinks/compositor_frame_sink_support_client.h",
+ "frame_sinks/compositor_frame_sink_support_manager.h",
+ "frame_sinks/direct_layer_tree_frame_sink.cc",
+ "frame_sinks/direct_layer_tree_frame_sink.h",
+ "frame_sinks/frame_eviction_manager.cc",
+ "frame_sinks/frame_eviction_manager.h",
+ "frame_sinks/frame_evictor.cc",
+ "frame_sinks/frame_evictor.h",
+ "frame_sinks/frame_sink_manager_client.h",
+ "frame_sinks/frame_sink_manager_impl.cc",
+ "frame_sinks/frame_sink_manager_impl.h",
+ "frame_sinks/gpu_compositor_frame_sink.cc",
+ "frame_sinks/gpu_compositor_frame_sink.h",
+ "frame_sinks/gpu_root_compositor_frame_sink.cc",
+ "frame_sinks/gpu_root_compositor_frame_sink.h",
+ "frame_sinks/primary_begin_frame_source.cc",
+ "frame_sinks/primary_begin_frame_source.h",
+ "frame_sinks/referenced_surface_tracker.cc",
+ "frame_sinks/referenced_surface_tracker.h",
+ "frame_sinks/surface_resource_holder.cc",
+ "frame_sinks/surface_resource_holder.h",
+ "frame_sinks/surface_resource_holder_client.h",
+ "hit_test/hit_test_aggregator.cc",
+ "hit_test/hit_test_aggregator.h",
+ "viz_service_export.h",
+ ]
+
+ defines = [ "VIZ_SERVICE_IMPLEMENTATION" ]
+
+ configs = [ "//build/config/compiler:no_size_t_to_int_warning" ]
+
+ deps = [
+ "//components/viz/common",
+
+ # Note that dependency on //gpu/ipc/client is for GpuMemoryBufferImpl. This
+ # dependency should not be in public_deps.
+ "//gpu/ipc/client",
+ "//gpu/ipc/service",
+ "//gpu/vulkan:features",
+ "//skia",
+ "//ui/display/types",
+ ]
+
+ public_deps = [
+ "//base",
+ "//cc",
+ "//cc/ipc:interfaces",
+ "//cc/surfaces",
+ "//gpu/command_buffer/client:gles2_interface",
+ "//gpu/ipc:command_buffer",
+ "//services/viz/hit_test/public/interfaces",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/latency",
+ ]
+
+ if (is_mac) {
+ sources += [
+ "display_embedder/compositor_overlay_candidate_validator_mac.h",
+ "display_embedder/compositor_overlay_candidate_validator_mac.mm",
+ ]
+ }
+
+ if (is_android) {
+ sources += [
+ "display_embedder/compositor_overlay_candidate_validator_android.cc",
+ "display_embedder/compositor_overlay_candidate_validator_android.h",
+ ]
+ }
+
+ if (use_ozone) {
+ sources += [
+ "display_embedder/compositor_overlay_candidate_validator_ozone.cc",
+ "display_embedder/compositor_overlay_candidate_validator_ozone.h",
+ "display_embedder/display_output_surface_ozone.cc",
+ "display_embedder/display_output_surface_ozone.h",
+ ]
+
+ public_deps += [ "//ui/ozone" ]
+ }
+
+ if (is_win) {
+ sources += [
+ "display_embedder/compositor_overlay_candidate_validator_win.cc",
+ "display_embedder/compositor_overlay_candidate_validator_win.h",
+ ]
+ }
+}
+
+viz_source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "display/display_scheduler_unittest.cc",
+ "display/display_unittest.cc",
+ "display/surface_aggregator_pixeltest.cc",
+ "display/surface_aggregator_unittest.cc",
+ "display_embedder/buffer_queue_unittest.cc",
+ "display_embedder/server_shared_bitmap_manager_unittest.cc",
+ "frame_sinks/compositor_frame_sink_support_unittest.cc",
+ "frame_sinks/direct_layer_tree_frame_sink_unittest.cc",
+ "frame_sinks/frame_sink_manager_unittest.cc",
+ "frame_sinks/referenced_surface_tracker_unittest.cc",
+ "frame_sinks/surface_references_unittest.cc",
+ "frame_sinks/surface_synchronization_unittest.cc",
+ "hit_test/hit_test_aggregator_unittest.cc",
+ ]
+
+ if (!use_aura && !is_mac) {
+ sources -= [ "display_embedder/buffer_queue_unittest.cc" ]
+ }
+
+ configs = [
+ "//build/config/compiler:no_size_t_to_int_warning",
+ "//third_party/khronos:khronos_headers",
+ ]
+
+ deps = [
+ ":service",
+ "//base",
+ "//base/test:test_support",
+ "//cc:test_support",
+ "//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/gtest",
+ "//ui/display/types",
+ ]
+}
+
+viz_source_set("perf_tests") {
+ testonly = true
+ sources = [
+ "display/surface_aggregator_perftest.cc",
+ ]
+
+ deps = [
+ ":service",
+ "//base",
+ "//cc:test_support",
+ "//cc/base",
+ "//testing/gtest",
+ "//testing/perf",
+ ]
+}
diff --git a/chromium/components/viz/service/DEPS b/chromium/components/viz/service/DEPS
new file mode 100644
index 00000000000..19ccf893a16
--- /dev/null
+++ b/chromium/components/viz/service/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ "+cc",
+ "+components/viz/service",
+ "+third_party/skia",
+ "+ui/gfx",
+ "+ui/gfx/geometry",
+ "+ui/latency",
+]
diff --git a/chromium/components/viz/service/display/DEPS b/chromium/components/viz/service/display/DEPS
new file mode 100644
index 00000000000..e8ecaf1670f
--- /dev/null
+++ b/chromium/components/viz/service/display/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+ "+base",
+ "+cc/base",
+ "+cc/benchmarks",
+ "+cc/output",
+ "+cc/quads",
+ "+cc/resources",
+ "+cc/scheduler",
+ "+cc/surfaces",
+ "+gpu/command_buffer/client",
+ "+gpu/command_buffer/common",
+ "+gpu/vulkan",
+ "+third_party/skia",
+ "+ui/gfx",
+ "+ui/latency",
+]
+
+specific_include_rules = {
+ ".*_(unit|pixel|perf)test\.cc": [
+ "+cc/test",
+ "+components/viz/service/frame_sinks",
+ "+gpu/GLES2",
+ ],
+}
diff --git a/chromium/components/viz/service/display/README.md b/chromium/components/viz/service/display/README.md
new file mode 100644
index 00000000000..9d70d664198
--- /dev/null
+++ b/chromium/components/viz/service/display/README.md
@@ -0,0 +1,12 @@
+# display/
+
+This directory is the implementation of the Display Compositor.
+
+The Display Compositor combines frames submitted to the viz service through
+frame sinks, and combines them into a single gpu or software resource to be
+presented to the user.
+
+This directory is agnostic w.r.t. platform-specific presentation details, which
+are abstracted behind OutputSurface and SoftwareOutputDevice. Through these
+APIs, it supports either compositing gpu resources (textures) into a single
+texture/framebuffer, or software resources (bitmaps) into a single bitmap.
diff --git a/chromium/components/viz/service/display/display.cc b/chromium/components/viz/service/display/display.cc
new file mode 100644
index 00000000000..2af77eaa229
--- /dev/null
+++ b/chromium/components/viz/service/display/display.cc
@@ -0,0 +1,434 @@
+// Copyright 2014 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/display/display.h"
+
+#include <stddef.h>
+
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/timer/elapsed_timer.h"
+#include "base/trace_event/trace_event.h"
+#include "cc/benchmarks/benchmark_instrumentation.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/direct_renderer.h"
+#include "cc/output/gl_renderer.h"
+#include "cc/output/software_renderer.h"
+#include "cc/output/texture_mailbox_deleter.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/surface.h"
+#include "cc/surfaces/surface_manager.h"
+#include "components/viz/common/display/renderer_settings.h"
+#include "components/viz/service/display/display_client.h"
+#include "components/viz/service/display/display_scheduler.h"
+#include "components/viz/service/display/surface_aggregator.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/vulkan/features.h"
+#include "ui/gfx/buffer_types.h"
+
+#if BUILDFLAG(ENABLE_VULKAN)
+#include "cc/output/vulkan_renderer.h"
+#endif
+
+namespace viz {
+
+Display::Display(
+ SharedBitmapManager* bitmap_manager,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ const RendererSettings& settings,
+ const FrameSinkId& frame_sink_id,
+ std::unique_ptr<cc::OutputSurface> output_surface,
+ std::unique_ptr<DisplayScheduler> scheduler,
+ std::unique_ptr<cc::TextureMailboxDeleter> texture_mailbox_deleter)
+ : bitmap_manager_(bitmap_manager),
+ gpu_memory_buffer_manager_(gpu_memory_buffer_manager),
+ settings_(settings),
+ frame_sink_id_(frame_sink_id),
+ output_surface_(std::move(output_surface)),
+ scheduler_(std::move(scheduler)),
+ texture_mailbox_deleter_(std::move(texture_mailbox_deleter)) {
+ DCHECK(output_surface_);
+ DCHECK(frame_sink_id_.is_valid());
+ if (scheduler_)
+ scheduler_->SetClient(this);
+}
+
+Display::~Display() {
+ // Only do this if Initialize() happened.
+ if (client_) {
+ if (auto* context = output_surface_->context_provider())
+ context->SetLostContextCallback(base::Closure());
+ if (scheduler_)
+ surface_manager_->RemoveObserver(scheduler_.get());
+ }
+ if (aggregator_) {
+ for (const auto& id_entry : aggregator_->previous_contained_surfaces()) {
+ cc::Surface* surface = surface_manager_->GetSurfaceForId(id_entry.first);
+ if (surface)
+ surface->RunDrawCallback();
+ }
+ }
+}
+
+void Display::Initialize(DisplayClient* client,
+ cc::SurfaceManager* surface_manager) {
+ DCHECK(client);
+ DCHECK(surface_manager);
+ client_ = client;
+ surface_manager_ = surface_manager;
+ if (scheduler_)
+ surface_manager_->AddObserver(scheduler_.get());
+
+ output_surface_->BindToClient(this);
+ InitializeRenderer();
+
+ if (auto* context = output_surface_->context_provider()) {
+ // This depends on assumptions that Display::Initialize will happen
+ // on the same callstack as the ContextProvider being created/initialized
+ // or else it could miss a callback before setting this.
+ context->SetLostContextCallback(base::Bind(
+ &Display::DidLoseContextProvider,
+ // Unretained is safe since the callback is unset in this class'
+ // destructor and is never posted.
+ base::Unretained(this)));
+ }
+}
+
+void Display::SetLocalSurfaceId(const LocalSurfaceId& id,
+ float device_scale_factor) {
+ if (current_surface_id_.local_surface_id() == id &&
+ device_scale_factor_ == device_scale_factor) {
+ return;
+ }
+
+ TRACE_EVENT0("viz", "Display::SetSurfaceId");
+ current_surface_id_ = SurfaceId(frame_sink_id_, id);
+ device_scale_factor_ = device_scale_factor;
+
+ UpdateRootSurfaceResourcesLocked();
+ if (scheduler_)
+ scheduler_->SetNewRootSurface(current_surface_id_);
+}
+
+void Display::SetVisible(bool visible) {
+ TRACE_EVENT1("viz", "Display::SetVisible", "visible", visible);
+ if (renderer_)
+ renderer_->SetVisible(visible);
+ if (scheduler_)
+ scheduler_->SetVisible(visible);
+ visible_ = visible;
+
+ if (!visible) {
+ // Damage tracker needs a full reset as renderer resources are dropped when
+ // not visible.
+ if (aggregator_ && current_surface_id_.is_valid())
+ aggregator_->SetFullDamageForSurface(current_surface_id_);
+ }
+}
+
+void Display::Resize(const gfx::Size& size) {
+ if (size == current_surface_size_)
+ return;
+
+ TRACE_EVENT0("viz", "Display::Resize");
+
+ // Need to ensure all pending swaps have executed before the window is
+ // resized, or D3D11 will scale the swap output.
+ if (settings_.finish_rendering_on_resize) {
+ if (!swapped_since_resize_ && scheduler_)
+ scheduler_->ForceImmediateSwapIfPossible();
+ if (swapped_since_resize_ && output_surface_ &&
+ output_surface_->context_provider())
+ output_surface_->context_provider()->ContextGL()->ShallowFinishCHROMIUM();
+ }
+ swapped_since_resize_ = false;
+ current_surface_size_ = size;
+ if (scheduler_)
+ scheduler_->DisplayResized();
+}
+
+void Display::SetColorSpace(const gfx::ColorSpace& blending_color_space,
+ const gfx::ColorSpace& device_color_space) {
+ blending_color_space_ = blending_color_space;
+ device_color_space_ = device_color_space;
+ if (aggregator_) {
+ aggregator_->SetOutputColorSpace(blending_color_space, device_color_space_);
+ }
+}
+
+void Display::SetOutputIsSecure(bool secure) {
+ if (secure == output_is_secure_)
+ return;
+ output_is_secure_ = secure;
+
+ if (aggregator_) {
+ aggregator_->set_output_is_secure(secure);
+ // Force a redraw.
+ if (current_surface_id_.is_valid())
+ aggregator_->SetFullDamageForSurface(current_surface_id_);
+ }
+}
+
+void Display::InitializeRenderer() {
+ // Not relevant for display compositor since it's not delegated.
+ constexpr bool delegated_sync_points_required = false;
+ resource_provider_ = base::MakeUnique<cc::ResourceProvider>(
+ output_surface_->context_provider(), bitmap_manager_,
+ gpu_memory_buffer_manager_, nullptr, delegated_sync_points_required,
+ settings_.enable_color_correct_rendering, settings_.resource_settings);
+
+ if (output_surface_->context_provider()) {
+ DCHECK(texture_mailbox_deleter_);
+ renderer_ = base::MakeUnique<cc::GLRenderer>(
+ &settings_, output_surface_.get(), resource_provider_.get(),
+ texture_mailbox_deleter_.get());
+ } else if (output_surface_->vulkan_context_provider()) {
+#if defined(ENABLE_VULKAN)
+ DCHECK(texture_mailbox_deleter_);
+ renderer_ = base::MakeUnique<cc::VulkanRenderer>(
+ &settings_, output_surface_.get(), resource_provider_.get(),
+ texture_mailbox_deleter_.get(), settings_.highp_threshold_min);
+#else
+ NOTREACHED();
+#endif
+ } else {
+ auto renderer = base::MakeUnique<cc::SoftwareRenderer>(
+ &settings_, output_surface_.get(), resource_provider_.get());
+ software_renderer_ = renderer.get();
+ renderer_ = std::move(renderer);
+ }
+
+ renderer_->Initialize();
+ renderer_->SetVisible(visible_);
+
+ // TODO(jbauman): Outputting an incomplete quad list doesn't work when using
+ // overlays.
+ bool output_partial_list = renderer_->use_partial_swap() &&
+ !output_surface_->GetOverlayCandidateValidator();
+ aggregator_.reset(new SurfaceAggregator(
+ surface_manager_, resource_provider_.get(), output_partial_list));
+ aggregator_->set_output_is_secure(output_is_secure_);
+ aggregator_->SetOutputColorSpace(blending_color_space_, device_color_space_);
+}
+
+void Display::UpdateRootSurfaceResourcesLocked() {
+ cc::Surface* surface = surface_manager_->GetSurfaceForId(current_surface_id_);
+ bool root_surface_resources_locked = !surface || !surface->HasActiveFrame();
+ if (scheduler_)
+ scheduler_->SetRootSurfaceResourcesLocked(root_surface_resources_locked);
+}
+
+void Display::DidLoseContextProvider() {
+ if (scheduler_)
+ scheduler_->OutputSurfaceLost();
+ // WARNING: The client may delete the Display in this method call. Do not
+ // make any additional references to members after this call.
+ client_->DisplayOutputSurfaceLost();
+}
+
+bool Display::DrawAndSwap() {
+ TRACE_EVENT0("viz", "Display::DrawAndSwap");
+
+ if (!current_surface_id_.is_valid()) {
+ TRACE_EVENT_INSTANT0("viz", "No root surface.", TRACE_EVENT_SCOPE_THREAD);
+ return false;
+ }
+
+ if (!output_surface_) {
+ TRACE_EVENT_INSTANT0("viz", "No output surface", TRACE_EVENT_SCOPE_THREAD);
+ return false;
+ }
+
+ base::ElapsedTimer aggregate_timer;
+ cc::CompositorFrame frame = aggregator_->Aggregate(current_surface_id_);
+ UMA_HISTOGRAM_COUNTS_1M("Compositing.SurfaceAggregator.AggregateUs",
+ aggregate_timer.Elapsed().InMicroseconds());
+
+ if (frame.render_pass_list.empty()) {
+ TRACE_EVENT_INSTANT0("viz", "Empty aggregated frame.",
+ TRACE_EVENT_SCOPE_THREAD);
+ return false;
+ }
+
+ // Run callbacks early to allow pipelining.
+ for (const auto& id_entry : aggregator_->previous_contained_surfaces()) {
+ cc::Surface* surface = surface_manager_->GetSurfaceForId(id_entry.first);
+ if (surface)
+ surface->RunDrawCallback();
+ }
+
+ frame.metadata.latency_info.insert(frame.metadata.latency_info.end(),
+ stored_latency_info_.begin(),
+ stored_latency_info_.end());
+ stored_latency_info_.clear();
+ bool have_copy_requests = false;
+ for (const auto& pass : frame.render_pass_list) {
+ have_copy_requests |= !pass->copy_requests.empty();
+ }
+
+ gfx::Size surface_size;
+ bool have_damage = false;
+ auto& last_render_pass = *frame.render_pass_list.back();
+ if (last_render_pass.output_rect.size() != current_surface_size_ &&
+ last_render_pass.damage_rect == last_render_pass.output_rect &&
+ !current_surface_size_.IsEmpty()) {
+ // Resize the output rect to the current surface size so that we won't
+ // skip the draw and so that the GL swap won't stretch the output.
+ last_render_pass.output_rect.set_size(current_surface_size_);
+ last_render_pass.damage_rect = last_render_pass.output_rect;
+ }
+ surface_size = last_render_pass.output_rect.size();
+ have_damage = !last_render_pass.damage_rect.size().IsEmpty();
+
+ bool size_matches = surface_size == current_surface_size_;
+ if (!size_matches)
+ TRACE_EVENT_INSTANT0("viz", "Size mismatch.", TRACE_EVENT_SCOPE_THREAD);
+
+ bool should_draw = have_copy_requests || (have_damage && size_matches);
+
+ // If the surface is suspended then the resources to be used by the draw are
+ // likely destroyed.
+ if (output_surface_->SurfaceIsSuspendForRecycle()) {
+ TRACE_EVENT_INSTANT0("viz", "Surface is suspended for recycle.",
+ TRACE_EVENT_SCOPE_THREAD);
+ should_draw = false;
+ }
+
+ client_->DisplayWillDrawAndSwap(should_draw, frame.render_pass_list);
+
+ if (should_draw) {
+ bool disable_image_filtering =
+ frame.metadata.is_resourceless_software_draw_with_scroll_or_animation;
+ if (software_renderer_) {
+ software_renderer_->SetDisablePictureQuadImageFiltering(
+ disable_image_filtering);
+ } else {
+ // This should only be set for software draws in synchronous compositor.
+ DCHECK(!disable_image_filtering);
+ }
+
+ base::ElapsedTimer draw_timer;
+ renderer_->DecideRenderPassAllocationsForFrame(frame.render_pass_list);
+ renderer_->DrawFrame(&frame.render_pass_list, device_scale_factor_,
+ current_surface_size_);
+ if (software_renderer_) {
+ UMA_HISTOGRAM_COUNTS_1M("Compositing.DirectRenderer.Software.DrawFrameUs",
+ draw_timer.Elapsed().InMicroseconds());
+ } else {
+ UMA_HISTOGRAM_COUNTS_1M("Compositing.DirectRenderer.GL.DrawFrameUs",
+ draw_timer.Elapsed().InMicroseconds());
+ }
+ } else {
+ TRACE_EVENT_INSTANT0("viz", "Draw skipped.", TRACE_EVENT_SCOPE_THREAD);
+ }
+
+ bool should_swap = should_draw && size_matches;
+ if (should_swap) {
+ swapped_since_resize_ = true;
+ for (auto& latency : frame.metadata.latency_info) {
+ TRACE_EVENT_WITH_FLOW1(
+ "input,benchmark", "LatencyInfo.Flow",
+ TRACE_ID_DONT_MANGLE(latency.trace_id()),
+ TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step",
+ "Display::DrawAndSwap");
+ }
+ cc::benchmark_instrumentation::IssueDisplayRenderingStatsEvent();
+ renderer_->SwapBuffers(std::move(frame.metadata.latency_info));
+ if (scheduler_)
+ scheduler_->DidSwapBuffers();
+ } else {
+ if (have_damage && !size_matches)
+ aggregator_->SetFullDamageForSurface(current_surface_id_);
+ TRACE_EVENT_INSTANT0("viz", "Swap skipped.", TRACE_EVENT_SCOPE_THREAD);
+
+ // Do not store more that the allowed size.
+ if (ui::LatencyInfo::Verify(frame.metadata.latency_info,
+ "Display::DrawAndSwap")) {
+ stored_latency_info_.insert(stored_latency_info_.end(),
+ frame.metadata.latency_info.begin(),
+ frame.metadata.latency_info.end());
+ }
+
+ if (scheduler_) {
+ scheduler_->DidSwapBuffers();
+ scheduler_->DidReceiveSwapBuffersAck();
+ }
+ }
+
+ client_->DisplayDidDrawAndSwap();
+ return true;
+}
+
+void Display::DidReceiveSwapBuffersAck() {
+ if (scheduler_)
+ scheduler_->DidReceiveSwapBuffersAck();
+ if (renderer_)
+ renderer_->SwapBuffersComplete();
+}
+
+void Display::DidReceiveTextureInUseResponses(
+ const gpu::TextureInUseResponses& responses) {
+ if (renderer_)
+ renderer_->DidReceiveTextureInUseResponses(responses);
+}
+
+void Display::SetNeedsRedrawRect(const gfx::Rect& damage_rect) {
+ aggregator_->SetFullDamageForSurface(current_surface_id_);
+ if (scheduler_) {
+ cc::BeginFrameAck ack;
+ ack.has_damage = true;
+ scheduler_->ProcessSurfaceDamage(current_surface_id_, ack, true);
+ }
+}
+
+bool Display::SurfaceDamaged(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack) {
+ bool display_damaged = false;
+ if (ack.has_damage) {
+ if (aggregator_ &&
+ aggregator_->previous_contained_surfaces().count(surface_id)) {
+ cc::Surface* surface = surface_manager_->GetSurfaceForId(surface_id);
+ if (surface) {
+ DCHECK(surface->HasActiveFrame());
+ if (surface->GetActiveFrame().resource_list.empty())
+ aggregator_->ReleaseResources(surface_id);
+ }
+ display_damaged = true;
+ if (surface_id == current_surface_id_)
+ UpdateRootSurfaceResourcesLocked();
+ } else if (surface_id == current_surface_id_) {
+ display_damaged = true;
+ UpdateRootSurfaceResourcesLocked();
+ }
+ }
+
+ return display_damaged;
+}
+
+void Display::SurfaceDiscarded(const SurfaceId& surface_id) {
+ if (aggregator_)
+ aggregator_->ReleaseResources(surface_id);
+}
+
+bool Display::SurfaceHasUndrawnFrame(const SurfaceId& surface_id) const {
+ if (!surface_manager_)
+ return false;
+
+ cc::Surface* surface = surface_manager_->GetSurfaceForId(surface_id);
+ if (!surface)
+ return false;
+
+ return surface->HasUndrawnActiveFrame();
+}
+
+const SurfaceId& Display::CurrentSurfaceId() {
+ return current_surface_id_;
+}
+
+void Display::ForceImmediateDrawAndSwapIfPossible() {
+ if (scheduler_)
+ scheduler_->ForceImmediateSwapIfPossible();
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display/display.h b/chromium/components/viz/service/display/display.h
new file mode 100644
index 00000000000..33f4a3f0123
--- /dev/null
+++ b/chromium/components/viz/service/display/display.h
@@ -0,0 +1,135 @@
+// Copyright 2014 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_DISPLAY_DISPLAY_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_DISPLAY_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "cc/output/output_surface_client.h"
+#include "cc/resources/returned_resource.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/surface_manager.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/service/display/display_scheduler.h"
+#include "components/viz/service/display/surface_aggregator.h"
+#include "components/viz/service/viz_service_export.h"
+#include "gpu/command_buffer/common/texture_in_use_response.h"
+#include "ui/gfx/color_space.h"
+#include "ui/latency/latency_info.h"
+
+namespace cc {
+class DirectRenderer;
+class OutputSurface;
+class RendererSettings;
+class ResourceProvider;
+class SoftwareRenderer;
+class TextureMailboxDeleter;
+} // namespace cc
+
+namespace gpu {
+class GpuMemoryBufferManager;
+}
+
+namespace gfx {
+class Size;
+}
+
+namespace viz {
+
+class DisplayClient;
+class SharedBitmapManager;
+
+// A Display produces a surface that can be used to draw to a physical display
+// (OutputSurface). The client is responsible for creating and sizing the
+// surface IDs used to draw into the display and deciding when to draw.
+class VIZ_SERVICE_EXPORT Display : public DisplaySchedulerClient,
+ public cc::OutputSurfaceClient {
+ public:
+ // The |begin_frame_source| and |scheduler| may be null (together). In that
+ // case, DrawAndSwap must be called externally when needed.
+ Display(SharedBitmapManager* bitmap_manager,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ const RendererSettings& settings,
+ const FrameSinkId& frame_sink_id,
+ std::unique_ptr<cc::OutputSurface> output_surface,
+ std::unique_ptr<DisplayScheduler> scheduler,
+ std::unique_ptr<cc::TextureMailboxDeleter> texture_mailbox_deleter);
+
+ ~Display() override;
+
+ void Initialize(DisplayClient* client, cc::SurfaceManager* surface_manager);
+
+ // device_scale_factor is used to communicate to the external window system
+ // what scale this was rendered at.
+ void SetLocalSurfaceId(const LocalSurfaceId& id, float device_scale_factor);
+ void SetVisible(bool visible);
+ void Resize(const gfx::Size& new_size);
+ void SetColorSpace(const gfx::ColorSpace& blending_color_space,
+ const gfx::ColorSpace& device_color_space);
+ void SetOutputIsSecure(bool secure);
+
+ const SurfaceId& CurrentSurfaceId();
+
+ // DisplaySchedulerClient implementation.
+ bool DrawAndSwap() override;
+ bool SurfaceHasUndrawnFrame(const SurfaceId& surface_id) const override;
+ bool SurfaceDamaged(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack) override;
+ void SurfaceDiscarded(const SurfaceId& surface_id) override;
+
+ // OutputSurfaceClient implementation.
+ void SetNeedsRedrawRect(const gfx::Rect& damage_rect) override;
+ void DidReceiveSwapBuffersAck() override;
+ void DidReceiveTextureInUseResponses(
+ const gpu::TextureInUseResponses& responses) override;
+
+ bool has_scheduler() const { return !!scheduler_; }
+ cc::DirectRenderer* renderer_for_testing() const { return renderer_.get(); }
+ size_t stored_latency_info_size_for_testing() const {
+ return stored_latency_info_.size();
+ }
+
+ void ForceImmediateDrawAndSwapIfPossible();
+
+ private:
+ void InitializeRenderer();
+ void UpdateRootSurfaceResourcesLocked();
+ void DidLoseContextProvider();
+
+ SharedBitmapManager* const bitmap_manager_;
+ gpu::GpuMemoryBufferManager* const gpu_memory_buffer_manager_;
+ const RendererSettings settings_;
+
+ DisplayClient* client_ = nullptr;
+ cc::SurfaceManager* surface_manager_ = nullptr;
+ const FrameSinkId frame_sink_id_;
+ SurfaceId current_surface_id_;
+ gfx::Size current_surface_size_;
+ float device_scale_factor_ = 1.f;
+ gfx::ColorSpace blending_color_space_ = gfx::ColorSpace::CreateSRGB();
+ gfx::ColorSpace device_color_space_ = gfx::ColorSpace::CreateSRGB();
+ bool visible_ = false;
+ bool swapped_since_resize_ = false;
+ bool output_is_secure_ = false;
+
+ std::unique_ptr<cc::OutputSurface> output_surface_;
+ std::unique_ptr<DisplayScheduler> scheduler_;
+ std::unique_ptr<cc::ResourceProvider> resource_provider_;
+ std::unique_ptr<SurfaceAggregator> aggregator_;
+ std::unique_ptr<cc::TextureMailboxDeleter> texture_mailbox_deleter_;
+ std::unique_ptr<cc::DirectRenderer> renderer_;
+ cc::SoftwareRenderer* software_renderer_ = nullptr;
+ std::vector<ui::LatencyInfo> stored_latency_info_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Display);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_DISPLAY_H_
diff --git a/chromium/components/viz/service/display/display_client.h b/chromium/components/viz/service/display/display_client.h
new file mode 100644
index 00000000000..b22a6938009
--- /dev/null
+++ b/chromium/components/viz/service/display/display_client.h
@@ -0,0 +1,24 @@
+// Copyright 2014 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_DISPLAY_DISPLAY_CLIENT_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_DISPLAY_CLIENT_H_
+
+#include "cc/quads/render_pass.h"
+
+namespace viz {
+
+class DisplayClient {
+ public:
+ virtual ~DisplayClient() {}
+ virtual void DisplayOutputSurfaceLost() = 0;
+ virtual void DisplayWillDrawAndSwap(
+ bool will_draw_and_swap,
+ const cc::RenderPassList& render_passes) = 0;
+ virtual void DisplayDidDrawAndSwap() = 0;
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_DISPLAY_CLIENT_H_
diff --git a/chromium/components/viz/service/display/display_scheduler.cc b/chromium/components/viz/service/display/display_scheduler.cc
new file mode 100644
index 00000000000..647554967a2
--- /dev/null
+++ b/chromium/components/viz/service/display/display_scheduler.cc
@@ -0,0 +1,492 @@
+// 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 "components/viz/service/display/display_scheduler.h"
+
+#include <vector>
+
+#include "base/auto_reset.h"
+#include "base/stl_util.h"
+#include "base/trace_event/trace_event.h"
+#include "cc/output/output_surface.h"
+#include "components/viz/common/surfaces/surface_info.h"
+
+namespace viz {
+
+DisplayScheduler::DisplayScheduler(cc::BeginFrameSource* begin_frame_source,
+ base::SingleThreadTaskRunner* task_runner,
+ int max_pending_swaps,
+ bool wait_for_all_surfaces_before_draw)
+ : client_(nullptr),
+ begin_frame_source_(begin_frame_source),
+ task_runner_(task_runner),
+ inside_surface_damaged_(false),
+ visible_(false),
+ output_surface_lost_(false),
+ root_surface_resources_locked_(true),
+ inside_begin_frame_deadline_interval_(false),
+ needs_draw_(false),
+ expecting_root_surface_damage_because_of_resize_(false),
+ has_pending_surfaces_(false),
+ next_swap_id_(1),
+ pending_swaps_(0),
+ max_pending_swaps_(max_pending_swaps),
+ wait_for_all_surfaces_before_draw_(wait_for_all_surfaces_before_draw),
+ observing_begin_frame_source_(false),
+ weak_ptr_factory_(this) {
+ begin_frame_deadline_closure_ = base::Bind(
+ &DisplayScheduler::OnBeginFrameDeadline, weak_ptr_factory_.GetWeakPtr());
+}
+
+DisplayScheduler::~DisplayScheduler() {
+ StopObservingBeginFrames();
+}
+
+void DisplayScheduler::SetClient(DisplaySchedulerClient* client) {
+ client_ = client;
+}
+
+void DisplayScheduler::SetVisible(bool visible) {
+ if (visible_ == visible)
+ return;
+
+ visible_ = visible;
+ // If going invisible, we'll stop observing begin frames once we try
+ // to draw and fail.
+ StartObservingBeginFrames();
+ ScheduleBeginFrameDeadline();
+}
+
+// If we try to draw when the root surface resources are locked, the
+// draw will fail.
+void DisplayScheduler::SetRootSurfaceResourcesLocked(bool locked) {
+ TRACE_EVENT1("viz", "DisplayScheduler::SetRootSurfaceResourcesLocked",
+ "locked", locked);
+ root_surface_resources_locked_ = locked;
+ ScheduleBeginFrameDeadline();
+}
+
+// This is used to force an immediate swap before a resize.
+void DisplayScheduler::ForceImmediateSwapIfPossible() {
+ TRACE_EVENT0("viz", "DisplayScheduler::ForceImmediateSwapIfPossible");
+ bool in_begin = inside_begin_frame_deadline_interval_;
+ bool did_draw = AttemptDrawAndSwap();
+ if (in_begin)
+ DidFinishFrame(did_draw);
+}
+
+void DisplayScheduler::DisplayResized() {
+ expecting_root_surface_damage_because_of_resize_ = true;
+ needs_draw_ = true;
+ ScheduleBeginFrameDeadline();
+}
+
+// Notification that there was a resize or the root surface changed and
+// that we should just draw immediately.
+void DisplayScheduler::SetNewRootSurface(const SurfaceId& root_surface_id) {
+ TRACE_EVENT0("viz", "DisplayScheduler::SetNewRootSurface");
+ root_surface_id_ = root_surface_id;
+ cc::BeginFrameAck ack;
+ ack.has_damage = true;
+ ProcessSurfaceDamage(root_surface_id, ack, true);
+}
+
+// Indicates that there was damage to one of the surfaces.
+// Has some logic to wait for multiple active surfaces before
+// triggering the deadline.
+void DisplayScheduler::ProcessSurfaceDamage(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack,
+ bool display_damaged) {
+ TRACE_EVENT1("viz", "DisplayScheduler::SurfaceDamaged", "surface_id",
+ surface_id.ToString());
+
+ // We may cause a new BeginFrame to be run inside this method, but to help
+ // avoid being reentrant to the caller of SurfaceDamaged, track when this is
+ // happening with |inside_surface_damaged_|.
+ base::AutoReset<bool> auto_reset(&inside_surface_damaged_, true);
+
+ if (display_damaged) {
+ needs_draw_ = true;
+
+ if (surface_id == root_surface_id_)
+ expecting_root_surface_damage_because_of_resize_ = false;
+
+ StartObservingBeginFrames();
+ }
+
+ // Update surface state.
+ bool valid_ack =
+ ack.sequence_number != cc::BeginFrameArgs::kInvalidFrameNumber;
+ if (valid_ack) {
+ auto it = surface_states_.find(surface_id);
+ if (it != surface_states_.end())
+ it->second.last_ack = ack;
+ else
+ valid_ack = false;
+ }
+
+ bool pending_surfaces_changed = false;
+ if (display_damaged || valid_ack)
+ pending_surfaces_changed = UpdateHasPendingSurfaces();
+
+ if (display_damaged || pending_surfaces_changed)
+ ScheduleBeginFrameDeadline();
+}
+
+bool DisplayScheduler::UpdateHasPendingSurfaces() {
+ // If we're not currently inside a deadline interval, we will call
+ // UpdateHasPendingSurfaces() again during OnBeginFrameImpl().
+ if (!inside_begin_frame_deadline_interval_ || !client_)
+ return false;
+
+ bool old_value = has_pending_surfaces_;
+
+ for (const std::pair<SurfaceId, SurfaceBeginFrameState>& entry :
+ surface_states_) {
+ const SurfaceId& surface_id = entry.first;
+ const SurfaceBeginFrameState& state = entry.second;
+
+ // Surface is ready if it hasn't received the current BeginFrame or receives
+ // BeginFrames from a different source and thus likely belongs to a
+ // different surface hierarchy.
+ uint32_t source_id = current_begin_frame_args_.source_id;
+ uint64_t sequence_number = current_begin_frame_args_.sequence_number;
+ if (!state.last_args.IsValid() || state.last_args.source_id != source_id ||
+ state.last_args.sequence_number != sequence_number) {
+ continue;
+ }
+
+ // Surface is ready if it has acknowledged the current BeginFrame.
+ if (state.last_ack.source_id == source_id &&
+ state.last_ack.sequence_number == sequence_number) {
+ continue;
+ }
+
+ // Surface is ready if there is an undrawn active CompositorFrame, because
+ // its producer is CompositorFrameAck throttled.
+ if (client_->SurfaceHasUndrawnFrame(entry.first))
+ continue;
+
+ has_pending_surfaces_ = true;
+ TRACE_EVENT_INSTANT2("viz", "DisplayScheduler::UpdateHasPendingSurfaces",
+ TRACE_EVENT_SCOPE_THREAD, "has_pending_surfaces",
+ has_pending_surfaces_, "pending_surface_id",
+ surface_id.ToString());
+ return has_pending_surfaces_ != old_value;
+ }
+ has_pending_surfaces_ = false;
+ TRACE_EVENT_INSTANT1("viz", "DisplayScheduler::UpdateHasPendingSurfaces",
+ TRACE_EVENT_SCOPE_THREAD, "has_pending_surfaces",
+ has_pending_surfaces_);
+ return has_pending_surfaces_ != old_value;
+}
+
+void DisplayScheduler::OutputSurfaceLost() {
+ TRACE_EVENT0("viz", "DisplayScheduler::OutputSurfaceLost");
+ output_surface_lost_ = true;
+ ScheduleBeginFrameDeadline();
+}
+
+bool DisplayScheduler::DrawAndSwap() {
+ TRACE_EVENT0("viz", "DisplayScheduler::DrawAndSwap");
+ DCHECK_LT(pending_swaps_, max_pending_swaps_);
+ DCHECK(!output_surface_lost_);
+
+ bool success = client_->DrawAndSwap();
+ if (!success)
+ return false;
+
+ needs_draw_ = false;
+ return true;
+}
+
+bool DisplayScheduler::OnBeginFrameDerivedImpl(const cc::BeginFrameArgs& args) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ TRACE_EVENT2("viz", "DisplayScheduler::BeginFrame", "args", args.AsValue(),
+ "now", now);
+
+ if (inside_surface_damaged_) {
+ // Repost this so that we don't run a missed BeginFrame on the same
+ // callstack. Otherwise we end up running unexpected scheduler actions
+ // immediately while inside some other action (such as submitting a
+ // CompositorFrame for a SurfaceFactory).
+ DCHECK_EQ(args.type, cc::BeginFrameArgs::MISSED);
+ DCHECK(missed_begin_frame_task_.IsCancelled());
+ missed_begin_frame_task_.Reset(base::Bind(
+ base::IgnoreResult(&DisplayScheduler::OnBeginFrameDerivedImpl),
+ // The CancelableCallback will not run after it is destroyed, which
+ // happens when |this| is destroyed.
+ base::Unretained(this), args));
+ task_runner_->PostTask(FROM_HERE, missed_begin_frame_task_.callback());
+ return true;
+ }
+
+ // Save the |BeginFrameArgs| as the callback (missed_begin_frame_task_) can be
+ // destroyed if we StopObservingBeginFrames(), and it would take the |args|
+ // with it. Instead save the args and cancel the |missed_begin_frame_task_|.
+ cc::BeginFrameArgs save_args = args;
+ // If we get another BeginFrame before a posted missed frame, just drop the
+ // missed frame. Also if this was the missed frame, drop the Callback inside
+ // it.
+ missed_begin_frame_task_.Cancel();
+
+ // If we get another BeginFrame before the previous deadline,
+ // synchronously trigger the previous deadline before progressing.
+ if (inside_begin_frame_deadline_interval_)
+ OnBeginFrameDeadline();
+
+ // Schedule the deadline.
+ current_begin_frame_args_ = save_args;
+ current_begin_frame_args_.deadline -=
+ cc::BeginFrameArgs::DefaultEstimatedParentDrawTime();
+ inside_begin_frame_deadline_interval_ = true;
+ UpdateHasPendingSurfaces();
+ ScheduleBeginFrameDeadline();
+
+ return true;
+}
+
+void DisplayScheduler::StartObservingBeginFrames() {
+ if (!observing_begin_frame_source_ && ShouldDraw()) {
+ begin_frame_source_->AddObserver(this);
+ observing_begin_frame_source_ = true;
+ }
+}
+
+void DisplayScheduler::StopObservingBeginFrames() {
+ if (observing_begin_frame_source_) {
+ begin_frame_source_->RemoveObserver(this);
+ observing_begin_frame_source_ = false;
+
+ // A missed BeginFrame may be queued, so drop that too if we're going to
+ // stop listening.
+ missed_begin_frame_task_.Cancel();
+ }
+}
+
+bool DisplayScheduler::ShouldDraw() {
+ // Note: When any of these cases becomes true, StartObservingBeginFrames must
+ // be called to ensure the draw will happen.
+ return needs_draw_ && !output_surface_lost_ && visible_;
+}
+
+void DisplayScheduler::OnBeginFrameSourcePausedChanged(bool paused) {
+ // BeginFrameSources used with DisplayScheduler do not make use of this
+ // feature.
+ if (paused)
+ NOTIMPLEMENTED();
+}
+
+void DisplayScheduler::OnSurfaceCreated(const SurfaceInfo& surface_info) {}
+
+void DisplayScheduler::OnSurfaceDestroyed(const SurfaceId& surface_id) {
+ auto it = surface_states_.find(surface_id);
+ if (it == surface_states_.end())
+ return;
+ surface_states_.erase(it);
+ if (UpdateHasPendingSurfaces())
+ ScheduleBeginFrameDeadline();
+}
+
+bool DisplayScheduler::OnSurfaceDamaged(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack) {
+ bool damaged = client_->SurfaceDamaged(surface_id, ack);
+ ProcessSurfaceDamage(surface_id, ack, damaged);
+
+ return damaged;
+}
+
+void DisplayScheduler::OnSurfaceDiscarded(const SurfaceId& surface_id) {
+ client_->SurfaceDiscarded(surface_id);
+}
+
+void DisplayScheduler::OnSurfaceDamageExpected(const SurfaceId& surface_id,
+ const cc::BeginFrameArgs& args) {
+ TRACE_EVENT1("viz", "DisplayScheduler::SurfaceDamageExpected", "surface_id",
+ surface_id.ToString());
+ // Insert a new state for the surface if we don't know of it yet. We don't use
+ // OnSurfaceCreated for this, because it may not be called if a
+ // CompositorFrameSinkSupport starts submitting frames to a different Display,
+ // but continues using the same Surface, or if a Surface does not activate its
+ // first CompositorFrame immediately.
+ surface_states_[surface_id].last_args = args;
+ if (UpdateHasPendingSurfaces())
+ ScheduleBeginFrameDeadline();
+}
+
+void DisplayScheduler::OnSurfaceWillDraw(const SurfaceId& surface_id) {}
+
+base::TimeTicks DisplayScheduler::DesiredBeginFrameDeadlineTime() const {
+ switch (AdjustedBeginFrameDeadlineMode()) {
+ case BeginFrameDeadlineMode::kImmediate:
+ return base::TimeTicks();
+ case BeginFrameDeadlineMode::kRegular:
+ return current_begin_frame_args_.deadline;
+ case BeginFrameDeadlineMode::kLate:
+ return current_begin_frame_args_.frame_time +
+ current_begin_frame_args_.interval;
+ case BeginFrameDeadlineMode::kNone:
+ return base::TimeTicks::Max();
+ default:
+ NOTREACHED();
+ return base::TimeTicks();
+ }
+}
+
+DisplayScheduler::BeginFrameDeadlineMode
+DisplayScheduler::AdjustedBeginFrameDeadlineMode() const {
+ BeginFrameDeadlineMode mode = DesiredBeginFrameDeadlineMode();
+
+ // In blocking mode, late and regular deadline should not apply. Wait
+ // indefinitely instead.
+ if (wait_for_all_surfaces_before_draw_ &&
+ (mode == BeginFrameDeadlineMode::kRegular ||
+ mode == BeginFrameDeadlineMode::kLate)) {
+ return BeginFrameDeadlineMode::kNone;
+ }
+
+ return mode;
+}
+
+DisplayScheduler::BeginFrameDeadlineMode
+DisplayScheduler::DesiredBeginFrameDeadlineMode() const {
+ if (output_surface_lost_) {
+ TRACE_EVENT_INSTANT0("viz", "Lost output surface",
+ TRACE_EVENT_SCOPE_THREAD);
+ return BeginFrameDeadlineMode::kImmediate;
+ }
+
+ if (pending_swaps_ >= max_pending_swaps_) {
+ TRACE_EVENT_INSTANT0("viz", "Swap throttled", TRACE_EVENT_SCOPE_THREAD);
+ return BeginFrameDeadlineMode::kLate;
+ }
+
+ if (root_surface_resources_locked_) {
+ TRACE_EVENT_INSTANT0("viz", "Root surface resources locked",
+ TRACE_EVENT_SCOPE_THREAD);
+ return BeginFrameDeadlineMode::kLate;
+ }
+
+ bool all_surfaces_ready = !has_pending_surfaces_ &&
+ root_surface_id_.is_valid() &&
+ !expecting_root_surface_damage_because_of_resize_;
+
+ // When no draw is needed, only allow an early deadline in full-pipe mode.
+ // This way, we can unblock the BeginFrame in full-pipe mode if no draw is
+ // necessary, but accommodate damage as a result of missed BeginFrames from
+ // clients otherwise.
+ bool allow_early_deadline_without_draw = wait_for_all_surfaces_before_draw_;
+
+ if (all_surfaces_ready &&
+ (needs_draw_ || allow_early_deadline_without_draw)) {
+ TRACE_EVENT_INSTANT0("viz", "All active surfaces ready",
+ TRACE_EVENT_SCOPE_THREAD);
+ return BeginFrameDeadlineMode::kImmediate;
+ }
+
+ if (!needs_draw_) {
+ TRACE_EVENT_INSTANT0("cc", "No damage yet", TRACE_EVENT_SCOPE_THREAD);
+ return BeginFrameDeadlineMode::kLate;
+ }
+
+ // TODO(mithro): Be smarter about resize deadlines.
+ if (expecting_root_surface_damage_because_of_resize_) {
+ TRACE_EVENT_INSTANT0("viz", "Entire display damaged",
+ TRACE_EVENT_SCOPE_THREAD);
+ return BeginFrameDeadlineMode::kLate;
+ }
+
+ TRACE_EVENT_INSTANT0("viz", "More damage expected soon",
+ TRACE_EVENT_SCOPE_THREAD);
+ return BeginFrameDeadlineMode::kRegular;
+}
+
+void DisplayScheduler::ScheduleBeginFrameDeadline() {
+ TRACE_EVENT0("viz", "DisplayScheduler::ScheduleBeginFrameDeadline");
+
+ // We need to wait for the next BeginFrame before scheduling a deadline.
+ if (!inside_begin_frame_deadline_interval_) {
+ TRACE_EVENT_INSTANT0("viz", "Waiting for next BeginFrame",
+ TRACE_EVENT_SCOPE_THREAD);
+ DCHECK(begin_frame_deadline_task_.IsCancelled());
+ return;
+ }
+
+ // Determine the deadline we want to use.
+ base::TimeTicks desired_deadline = DesiredBeginFrameDeadlineTime();
+
+ // Avoid re-scheduling the deadline if it's already correctly scheduled.
+ if (!begin_frame_deadline_task_.IsCancelled() &&
+ desired_deadline == begin_frame_deadline_task_time_) {
+ TRACE_EVENT_INSTANT0("viz", "Using existing deadline",
+ TRACE_EVENT_SCOPE_THREAD);
+ return;
+ }
+
+ // Schedule the deadline.
+ begin_frame_deadline_task_time_ = desired_deadline;
+ begin_frame_deadline_task_.Cancel();
+
+ if (begin_frame_deadline_task_time_ == base::TimeTicks::Max()) {
+ TRACE_EVENT_INSTANT0("cc", "Using infinite deadline",
+ TRACE_EVENT_SCOPE_THREAD);
+ return;
+ }
+
+ begin_frame_deadline_task_.Reset(begin_frame_deadline_closure_);
+ base::TimeDelta delta =
+ std::max(base::TimeDelta(), desired_deadline - base::TimeTicks::Now());
+ task_runner_->PostDelayedTask(FROM_HERE,
+ begin_frame_deadline_task_.callback(), delta);
+ TRACE_EVENT2("viz", "Using new deadline", "delta", delta.ToInternalValue(),
+ "desired_deadline", desired_deadline);
+}
+
+bool DisplayScheduler::AttemptDrawAndSwap() {
+ inside_begin_frame_deadline_interval_ = false;
+ begin_frame_deadline_task_.Cancel();
+ begin_frame_deadline_task_time_ = base::TimeTicks();
+
+ if (ShouldDraw()) {
+ if (pending_swaps_ < max_pending_swaps_ && !root_surface_resources_locked_)
+ return DrawAndSwap();
+ } else {
+ // We are going idle, so reset expectations.
+ // TODO(eseckler): Should we avoid going idle if
+ // |expecting_root_surface_damage_because_of_resize_| is true?
+ expecting_root_surface_damage_because_of_resize_ = false;
+
+ StopObservingBeginFrames();
+ }
+ return false;
+}
+
+void DisplayScheduler::OnBeginFrameDeadline() {
+ TRACE_EVENT0("viz", "DisplayScheduler::OnBeginFrameDeadline");
+ DCHECK(inside_begin_frame_deadline_interval_);
+
+ bool did_draw = AttemptDrawAndSwap();
+ DidFinishFrame(did_draw);
+}
+
+void DisplayScheduler::DidFinishFrame(bool did_draw) {
+ DCHECK(begin_frame_source_);
+ // TODO(eseckler): Let client know that frame was completed.
+ begin_frame_source_->DidFinishFrame(this);
+}
+
+void DisplayScheduler::DidSwapBuffers() {
+ pending_swaps_++;
+ uint32_t swap_id = next_swap_id_++;
+ TRACE_EVENT_ASYNC_BEGIN0("viz", "DisplayScheduler:pending_swaps", swap_id);
+}
+
+void DisplayScheduler::DidReceiveSwapBuffersAck() {
+ uint32_t swap_id = next_swap_id_ - pending_swaps_;
+ pending_swaps_--;
+ TRACE_EVENT_ASYNC_END0("viz", "DisplayScheduler:pending_swaps", swap_id);
+ ScheduleBeginFrameDeadline();
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display/display_scheduler.h b/chromium/components/viz/service/display/display_scheduler.h
new file mode 100644
index 00000000000..652208db836
--- /dev/null
+++ b/chromium/components/viz/service/display/display_scheduler.h
@@ -0,0 +1,136 @@
+// 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.
+
+#ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_DISPLAY_SCHEDULER_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_DISPLAY_SCHEDULER_H_
+
+#include <memory>
+
+#include "base/cancelable_callback.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/surface_observer.h"
+#include "components/viz/common/display/renderer_settings.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+class BeginFrameSource;
+class SurfaceInfo;
+
+class VIZ_SERVICE_EXPORT DisplaySchedulerClient {
+ public:
+ virtual ~DisplaySchedulerClient() {}
+
+ virtual bool DrawAndSwap() = 0;
+ virtual bool SurfaceHasUndrawnFrame(const SurfaceId& surface_id) const = 0;
+ virtual bool SurfaceDamaged(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack) = 0;
+ virtual void SurfaceDiscarded(const SurfaceId& surface_id) = 0;
+};
+
+class VIZ_SERVICE_EXPORT DisplayScheduler : public cc::BeginFrameObserverBase,
+ public cc::SurfaceObserver {
+ public:
+ DisplayScheduler(cc::BeginFrameSource* begin_frame_source,
+ base::SingleThreadTaskRunner* task_runner,
+ int max_pending_swaps,
+ bool wait_for_all_surfaces_before_draw = false);
+ ~DisplayScheduler() override;
+
+ void SetClient(DisplaySchedulerClient* client);
+
+ void SetVisible(bool visible);
+ void SetRootSurfaceResourcesLocked(bool locked);
+ void ForceImmediateSwapIfPossible();
+ virtual void DisplayResized();
+ virtual void SetNewRootSurface(const SurfaceId& root_surface_id);
+ virtual void ProcessSurfaceDamage(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack,
+ bool display_damaged);
+
+ virtual void DidSwapBuffers();
+ void DidReceiveSwapBuffersAck();
+
+ void OutputSurfaceLost();
+
+ // BeginFrameObserverBase implementation.
+ bool OnBeginFrameDerivedImpl(const cc::BeginFrameArgs& args) override;
+ void OnBeginFrameSourcePausedChanged(bool paused) override;
+
+ // SurfaceObserver implementation.
+ 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;
+ void OnSurfaceWillDraw(const SurfaceId& surface_id) override;
+
+ protected:
+ enum class BeginFrameDeadlineMode { kImmediate, kRegular, kLate, kNone };
+ base::TimeTicks DesiredBeginFrameDeadlineTime() const;
+ BeginFrameDeadlineMode AdjustedBeginFrameDeadlineMode() const;
+ BeginFrameDeadlineMode DesiredBeginFrameDeadlineMode() const;
+ virtual void ScheduleBeginFrameDeadline();
+ bool AttemptDrawAndSwap();
+ void OnBeginFrameDeadline();
+ bool DrawAndSwap();
+ void StartObservingBeginFrames();
+ void StopObservingBeginFrames();
+ bool ShouldDraw();
+ void DidFinishFrame(bool did_draw);
+ // Updates |has_pending_surfaces_| and returns whether its value changed.
+ bool UpdateHasPendingSurfaces();
+
+ DisplaySchedulerClient* client_;
+ cc::BeginFrameSource* begin_frame_source_;
+ base::SingleThreadTaskRunner* task_runner_;
+
+ cc::BeginFrameArgs current_begin_frame_args_;
+ base::Closure begin_frame_deadline_closure_;
+ base::CancelableClosure begin_frame_deadline_task_;
+ base::TimeTicks begin_frame_deadline_task_time_;
+
+ base::CancelableClosure missed_begin_frame_task_;
+ bool inside_surface_damaged_;
+
+ bool visible_;
+ bool output_surface_lost_;
+ bool root_surface_resources_locked_;
+
+ bool inside_begin_frame_deadline_interval_;
+ bool needs_draw_;
+ bool expecting_root_surface_damage_because_of_resize_;
+ bool has_pending_surfaces_;
+
+ struct SurfaceBeginFrameState {
+ cc::BeginFrameArgs last_args;
+ cc::BeginFrameAck last_ack;
+ };
+ base::flat_map<SurfaceId, SurfaceBeginFrameState> surface_states_;
+
+ int next_swap_id_;
+ int pending_swaps_;
+ int max_pending_swaps_;
+ bool wait_for_all_surfaces_before_draw_;
+
+ bool observing_begin_frame_source_;
+
+ SurfaceId root_surface_id_;
+
+ base::WeakPtrFactory<DisplayScheduler> weak_ptr_factory_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DisplayScheduler);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_DISPLAY_SCHEDULER_H_
diff --git a/chromium/components/viz/service/display/display_scheduler_unittest.cc b/chromium/components/viz/service/display/display_scheduler_unittest.cc
new file mode 100644
index 00000000000..1f1f84927ba
--- /dev/null
+++ b/chromium/components/viz/service/display/display_scheduler_unittest.cc
@@ -0,0 +1,734 @@
+// 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 "components/viz/service/display/display_scheduler.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/test/null_task_runner.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/trace_event/trace_event.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/test/fake_external_begin_frame_source.h"
+#include "cc/test/scheduler_test_common.h"
+#include "components/viz/common/surfaces/surface_info.h"
+#include "components/viz/service/display/display.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace viz {
+namespace {
+
+const int kMaxPendingSwaps = 1;
+
+static constexpr FrameSinkId kArbitraryFrameSinkId(1, 1);
+
+class FakeDisplaySchedulerClient : public DisplaySchedulerClient {
+ public:
+ FakeDisplaySchedulerClient()
+ : draw_and_swap_count_(0), next_draw_and_swap_fails_(false) {}
+
+ ~FakeDisplaySchedulerClient() override {}
+
+ bool DrawAndSwap() override {
+ draw_and_swap_count_++;
+
+ bool success = !next_draw_and_swap_fails_;
+ next_draw_and_swap_fails_ = false;
+
+ if (success)
+ undrawn_surfaces_.clear();
+ return success;
+ }
+
+ bool SurfaceHasUndrawnFrame(const SurfaceId& surface_id) const override {
+ return base::ContainsKey(undrawn_surfaces_, surface_id);
+ }
+
+ bool SurfaceDamaged(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack) override {
+ return false;
+ }
+
+ void SurfaceDiscarded(const SurfaceId& surface_id) override {}
+
+ int draw_and_swap_count() const { return draw_and_swap_count_; }
+
+ void SetNextDrawAndSwapFails() { next_draw_and_swap_fails_ = true; }
+
+ void SurfaceDamaged(const SurfaceId& surface_id) {
+ undrawn_surfaces_.insert(surface_id);
+ }
+
+ protected:
+ int draw_and_swap_count_;
+ bool next_draw_and_swap_fails_;
+ std::set<SurfaceId> undrawn_surfaces_;
+};
+
+class TestDisplayScheduler : public DisplayScheduler {
+ public:
+ TestDisplayScheduler(cc::BeginFrameSource* begin_frame_source,
+ cc::SurfaceManager* surface_manager,
+ base::SingleThreadTaskRunner* task_runner,
+ int max_pending_swaps,
+ bool wait_for_all_surfaces_before_draw)
+ : DisplayScheduler(begin_frame_source,
+ task_runner,
+ max_pending_swaps,
+ wait_for_all_surfaces_before_draw),
+ scheduler_begin_frame_deadline_count_(0) {}
+
+ base::TimeTicks DesiredBeginFrameDeadlineTimeForTest() {
+ return DesiredBeginFrameDeadlineTime();
+ }
+
+ void BeginFrameDeadlineForTest() {
+ // Ensure that any missed BeginFrames were handled by the scheduler. We need
+ // to run the scheduled task ourselves since the NullTaskRunner won't.
+ if (!missed_begin_frame_task_.IsCancelled())
+ missed_begin_frame_task_.callback().Run();
+ OnBeginFrameDeadline();
+ }
+
+ void ScheduleBeginFrameDeadline() override {
+ scheduler_begin_frame_deadline_count_++;
+ DisplayScheduler::ScheduleBeginFrameDeadline();
+ }
+
+ int scheduler_begin_frame_deadline_count() {
+ return scheduler_begin_frame_deadline_count_;
+ }
+
+ bool inside_begin_frame_deadline_interval() {
+ return inside_begin_frame_deadline_interval_;
+ }
+
+ bool has_pending_surfaces() { return has_pending_surfaces_; }
+
+ protected:
+ int scheduler_begin_frame_deadline_count_;
+};
+
+class DisplaySchedulerTest : public testing::Test {
+ public:
+ explicit DisplaySchedulerTest(bool wait_for_all_surfaces_before_draw = false)
+ : fake_begin_frame_source_(0.f, false),
+ task_runner_(new base::NullTaskRunner),
+ scheduler_(&fake_begin_frame_source_,
+ &surface_manager_,
+ task_runner_.get(),
+ kMaxPendingSwaps,
+ wait_for_all_surfaces_before_draw) {
+ now_src_.Advance(base::TimeDelta::FromMicroseconds(10000));
+ surface_manager_.AddObserver(&scheduler_);
+ scheduler_.SetClient(&client_);
+ }
+
+ ~DisplaySchedulerTest() override {
+ surface_manager_.RemoveObserver(&scheduler_);
+ }
+
+ void SetUp() override { scheduler_.SetRootSurfaceResourcesLocked(false); }
+
+ void AdvanceTimeAndBeginFrameForTest(
+ const std::vector<SurfaceId>& observing_surfaces) {
+ now_src_.Advance(base::TimeDelta::FromMicroseconds(10000));
+ // FakeBeginFrameSource deals with |source_id| and |sequence_number|.
+ last_begin_frame_args_ = fake_begin_frame_source_.CreateBeginFrameArgs(
+ BEGINFRAME_FROM_HERE, &now_src_);
+ fake_begin_frame_source_.TestOnBeginFrame(last_begin_frame_args_);
+ for (const auto& surface_id : observing_surfaces)
+ scheduler_.OnSurfaceDamageExpected(surface_id, last_begin_frame_args_);
+ }
+
+ void SurfaceDamaged(const SurfaceId& surface_id) {
+ client_.SurfaceDamaged(surface_id);
+ scheduler_.ProcessSurfaceDamage(surface_id, AckForCurrentBeginFrame(),
+ true);
+ }
+
+ protected:
+ base::SimpleTestTickClock& now_src() { return now_src_; }
+ FakeDisplaySchedulerClient& client() { return client_; }
+ DisplayScheduler& scheduler() { return scheduler_; }
+ cc::BeginFrameAck AckForCurrentBeginFrame() {
+ DCHECK(last_begin_frame_args_.IsValid());
+ return cc::BeginFrameAck(last_begin_frame_args_.source_id,
+ last_begin_frame_args_.sequence_number, true);
+ }
+
+ cc::FakeExternalBeginFrameSource fake_begin_frame_source_;
+ cc::BeginFrameArgs last_begin_frame_args_;
+
+ base::SimpleTestTickClock now_src_;
+ scoped_refptr<base::NullTaskRunner> task_runner_;
+ cc::SurfaceManager surface_manager_;
+ FakeDisplaySchedulerClient client_;
+ TestDisplayScheduler scheduler_;
+};
+
+TEST_F(DisplaySchedulerTest, ResizeHasLateDeadlineUntilNewRootSurface) {
+ SurfaceId root_surface_id1(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId root_surface_id2(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(3, base::UnguessableToken::Create()));
+ base::TimeTicks late_deadline;
+
+ scheduler_.SetVisible(true);
+
+ // Go trough an initial BeginFrame cycle with the root surface.
+ AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
+ scheduler_.SetNewRootSurface(root_surface_id1);
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Resize on the next begin frame cycle should cause the deadline to wait
+ // for a new root surface.
+ AdvanceTimeAndBeginFrameForTest({root_surface_id1});
+ late_deadline = now_src().NowTicks() + cc::BeginFrameArgs::DefaultInterval();
+ SurfaceDamaged(sid1);
+ EXPECT_GT(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.DisplayResized();
+ EXPECT_EQ(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.OnSurfaceDestroyed(root_surface_id1);
+ scheduler_.SetNewRootSurface(root_surface_id2);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Verify deadline goes back to normal after resize.
+ late_deadline = now_src().NowTicks() + cc::BeginFrameArgs::DefaultInterval();
+ AdvanceTimeAndBeginFrameForTest({root_surface_id2, sid1});
+ SurfaceDamaged(sid1);
+ EXPECT_GT(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ SurfaceDamaged(root_surface_id2);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+}
+
+TEST_F(DisplaySchedulerTest, ResizeHasLateDeadlineUntilDamagedSurface) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+ base::TimeTicks late_deadline;
+
+ scheduler_.SetVisible(true);
+
+ // Go trough an initial BeginFrame cycle with the root surface.
+ AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
+ scheduler_.SetNewRootSurface(root_surface_id);
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Resize on the next begin frame cycle should cause the deadline to wait
+ // for a new root surface.
+ AdvanceTimeAndBeginFrameForTest({root_surface_id});
+ late_deadline = now_src().NowTicks() + cc::BeginFrameArgs::DefaultInterval();
+ SurfaceDamaged(sid1);
+ EXPECT_GT(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.DisplayResized();
+ EXPECT_EQ(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ SurfaceDamaged(root_surface_id);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Verify deadline goes back to normal after resize.
+ AdvanceTimeAndBeginFrameForTest({root_surface_id, sid1});
+ late_deadline = now_src().NowTicks() + cc::BeginFrameArgs::DefaultInterval();
+ SurfaceDamaged(sid1);
+ EXPECT_GT(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ SurfaceDamaged(root_surface_id);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+}
+
+TEST_F(DisplaySchedulerTest, SurfaceDamaged) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+ SurfaceId sid2(kArbitraryFrameSinkId,
+ LocalSurfaceId(3, base::UnguessableToken::Create()));
+
+ scheduler_.SetVisible(true);
+ scheduler_.SetNewRootSurface(root_surface_id);
+
+ // Set surface1 as active via SurfaceDamageExpected().
+ AdvanceTimeAndBeginFrameForTest({sid1});
+
+ // Damage only from surface 2 (inactive) does not trigger deadline early.
+ SurfaceDamaged(sid2);
+ EXPECT_TRUE(scheduler_.has_pending_surfaces());
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+
+ // Damage from surface 1 triggers deadline early.
+ SurfaceDamaged(sid1);
+ EXPECT_FALSE(scheduler_.has_pending_surfaces());
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Set both surface 1 and 2 as active via SurfaceDamageExpected().
+ AdvanceTimeAndBeginFrameForTest({sid1, sid2});
+
+ // Deadline doesn't trigger early until surface 1 and 2 are both damaged.
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ SurfaceDamaged(sid1);
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ SurfaceDamaged(sid2);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Surface damage with |!has_damage| triggers early deadline if other damage
+ // exists.
+ AdvanceTimeAndBeginFrameForTest({sid1, sid2});
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ SurfaceDamaged(sid2);
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ cc::BeginFrameAck ack = AckForCurrentBeginFrame();
+ ack.has_damage = false;
+ scheduler_.ProcessSurfaceDamage(sid1, ack, false);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Surface damage with |!has_damage| does not trigger early deadline if no
+ // other damage exists.
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ ack = AckForCurrentBeginFrame();
+ ack.has_damage = false;
+ scheduler_.ProcessSurfaceDamage(sid1, ack, false);
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // System should be idle now.
+ AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
+ EXPECT_FALSE(scheduler_.inside_begin_frame_deadline_interval());
+
+ // Surface damage with |!display_damaged| does not affect needs_draw and
+ // scheduler stays idle.
+ scheduler_.ProcessSurfaceDamage(sid1, AckForCurrentBeginFrame(), false);
+ EXPECT_FALSE(scheduler_.inside_begin_frame_deadline_interval());
+
+ // Deadline should trigger early if child surfaces are idle and
+ // we get damage on the root surface.
+ scheduler_.OnSurfaceDamageExpected(root_surface_id, last_begin_frame_args_);
+ EXPECT_FALSE(scheduler_.inside_begin_frame_deadline_interval());
+ SurfaceDamaged(root_surface_id);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+}
+
+class DisplaySchedulerWaitForAllSurfacesTest : public DisplaySchedulerTest {
+ public:
+ DisplaySchedulerWaitForAllSurfacesTest()
+ : DisplaySchedulerTest(true /* wait_for_all_surfaces_before_draw */) {}
+};
+
+TEST_F(DisplaySchedulerWaitForAllSurfacesTest, WaitForAllSurfacesBeforeDraw) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+ SurfaceId sid2(kArbitraryFrameSinkId,
+ LocalSurfaceId(3, base::UnguessableToken::Create()));
+
+ scheduler_.SetVisible(true);
+ scheduler_.SetNewRootSurface(root_surface_id);
+
+ // Set surface1 as active via SurfaceDamageExpected().
+ AdvanceTimeAndBeginFrameForTest({sid1});
+
+ // Deadline is blocked indefinitely until surface 1 is damaged.
+ EXPECT_EQ(base::TimeTicks::Max(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+
+ // Damage only from surface 2 (inactive) does not change deadline.
+ SurfaceDamaged(sid2);
+ EXPECT_TRUE(scheduler_.has_pending_surfaces());
+ EXPECT_EQ(base::TimeTicks::Max(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+
+ // Damage from surface 1 triggers deadline immediately.
+ SurfaceDamaged(sid1);
+ EXPECT_FALSE(scheduler_.has_pending_surfaces());
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Surface damage with |!has_damage| triggers immediate deadline if other
+ // damage exists.
+ AdvanceTimeAndBeginFrameForTest({sid1, sid2});
+ EXPECT_EQ(base::TimeTicks::Max(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ SurfaceDamaged(sid2);
+ EXPECT_EQ(base::TimeTicks::Max(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ cc::BeginFrameAck ack = AckForCurrentBeginFrame();
+ ack.has_damage = false;
+ scheduler_.ProcessSurfaceDamage(sid1, ack, false);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // Surface damage with |!has_damage| also triggers immediate deadline even if
+ // no other damage exists.
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ EXPECT_EQ(base::TimeTicks::Max(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ ack = AckForCurrentBeginFrame();
+ ack.has_damage = false;
+ scheduler_.ProcessSurfaceDamage(sid1, ack, false);
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // System should be idle now because we had a frame without damage. Restore it
+ // to active state (DisplayScheduler observing BeginFrames) for the next test.
+ AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
+ EXPECT_FALSE(scheduler_.inside_begin_frame_deadline_interval());
+ SurfaceDamaged(sid1);
+ scheduler_.BeginFrameDeadlineForTest();
+
+ // BeginFrame without expected surface damage triggers immediate deadline.
+ AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
+ EXPECT_TRUE(scheduler_.inside_begin_frame_deadline_interval());
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.BeginFrameDeadlineForTest();
+}
+
+TEST_F(DisplaySchedulerTest, OutputSurfaceLost) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+
+ scheduler_.SetVisible(true);
+ scheduler_.SetNewRootSurface(root_surface_id);
+
+ // DrawAndSwap normally.
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ EXPECT_EQ(0, client_.draw_and_swap_count());
+ SurfaceDamaged(sid1);
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+
+ // Deadline triggers immediately on OutputSurfaceLost.
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.OutputSurfaceLost();
+ EXPECT_GE(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+
+ // Deadline does not DrawAndSwap after OutputSurfaceLost.
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+ SurfaceDamaged(sid1);
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+}
+
+TEST_F(DisplaySchedulerTest, VisibleWithoutDamageNoTicks) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+
+ EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
+ scheduler_.SetVisible(true);
+
+ // When becoming visible, don't start listening for begin frames until there
+ // is some damage.
+ EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
+ scheduler_.SetNewRootSurface(root_surface_id);
+
+ EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
+}
+
+TEST_F(DisplaySchedulerTest, VisibleWithDamageTicks) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+
+ scheduler_.SetNewRootSurface(root_surface_id);
+
+ // When there is damage, start listening for begin frames once becoming
+ // visible.
+ EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
+ scheduler_.SetVisible(true);
+
+ EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
+}
+
+TEST_F(DisplaySchedulerTest, Visibility) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+
+ // Set the root surface.
+ scheduler_.SetNewRootSurface(root_surface_id);
+ scheduler_.SetVisible(true);
+ EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
+
+ // DrawAndSwap normally.
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ EXPECT_EQ(0, client_.draw_and_swap_count());
+ SurfaceDamaged(sid1);
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+
+ // Become not visible.
+ scheduler_.SetVisible(false);
+
+ // It will stop listening for begin frames after the current deadline.
+ EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
+
+ // Deadline does not DrawAndSwap when not visible.
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+ // Now it stops listening for begin frames.
+ EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
+
+ // Does not start listening for begin frames when becoming visible without
+ // damage.
+ scheduler_.SetVisible(true);
+ EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
+ scheduler_.SetVisible(false);
+
+ // Does not start listening for begin frames when damage arrives.
+ SurfaceDamaged(sid1);
+ EXPECT_EQ(0u, fake_begin_frame_source_.num_observers());
+
+ // But does when becoming visible with damage again.
+ scheduler_.SetVisible(true);
+ EXPECT_EQ(1u, fake_begin_frame_source_.num_observers());
+}
+
+TEST_F(DisplaySchedulerTest, ResizeCausesSwap) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+
+ scheduler_.SetVisible(true);
+ scheduler_.SetNewRootSurface(root_surface_id);
+
+ // DrawAndSwap normally.
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ EXPECT_EQ(0, client_.draw_and_swap_count());
+ SurfaceDamaged(sid1);
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+
+ scheduler_.DisplayResized();
+ AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
+ // DisplayResized should trigger a swap to happen.
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(2, client_.draw_and_swap_count());
+}
+
+TEST_F(DisplaySchedulerTest, RootSurfaceResourcesLocked) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+ base::TimeTicks late_deadline;
+
+ scheduler_.SetVisible(true);
+ scheduler_.SetNewRootSurface(root_surface_id);
+
+ // DrawAndSwap normally.
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ EXPECT_EQ(0, client_.draw_and_swap_count());
+ SurfaceDamaged(sid1);
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+
+ // Deadline triggers late while root resources are locked.
+ AdvanceTimeAndBeginFrameForTest({sid1});
+ late_deadline = now_src().NowTicks() + cc::BeginFrameArgs::DefaultInterval();
+ SurfaceDamaged(sid1);
+ EXPECT_GT(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.SetRootSurfaceResourcesLocked(true);
+ EXPECT_EQ(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+
+ // Deadline does not DrawAndSwap while root resources are locked.
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+ SurfaceDamaged(sid1);
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+
+ // Deadline triggers normally when root resources are unlocked.
+ AdvanceTimeAndBeginFrameForTest({sid1, root_surface_id});
+ late_deadline = now_src().NowTicks() + cc::BeginFrameArgs::DefaultInterval();
+ SurfaceDamaged(sid1);
+ EXPECT_EQ(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ scheduler_.SetRootSurfaceResourcesLocked(false);
+ SurfaceDamaged(root_surface_id);
+ EXPECT_EQ(base::TimeTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(2, client_.draw_and_swap_count());
+}
+
+TEST_F(DisplaySchedulerTest, DidSwapBuffers) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+ SurfaceId sid2(kArbitraryFrameSinkId,
+ LocalSurfaceId(3, base::UnguessableToken::Create()));
+
+ scheduler_.SetVisible(true);
+ scheduler_.SetNewRootSurface(root_surface_id);
+
+ // Set surface 1 and 2 as active.
+ AdvanceTimeAndBeginFrameForTest({sid1, sid2});
+
+ // DrawAndSwap normally.
+ EXPECT_LT(now_src().NowTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ EXPECT_EQ(0, client_.draw_and_swap_count());
+ SurfaceDamaged(sid1);
+ SurfaceDamaged(sid2);
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+ scheduler_.DidSwapBuffers();
+
+ // Deadline triggers late when swap throttled.
+ AdvanceTimeAndBeginFrameForTest({sid1, sid2});
+ base::TimeTicks late_deadline =
+ now_src().NowTicks() + cc::BeginFrameArgs::DefaultInterval();
+ // Damage surface 1, but not surface 2 so we avoid triggering deadline
+ // early because all surfaces are ready.
+ SurfaceDamaged(sid1);
+ EXPECT_EQ(late_deadline, scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+
+ // Don't draw and swap in deadline while swap throttled.
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+
+ // Deadline triggers normally once not swap throttled.
+ // Damage from previous BeginFrame should cary over, so don't damage again.
+ scheduler_.DidReceiveSwapBuffersAck();
+ AdvanceTimeAndBeginFrameForTest({sid2});
+ base::TimeTicks expected_deadline =
+ scheduler_.LastUsedBeginFrameArgs().deadline -
+ cc::BeginFrameArgs::DefaultEstimatedParentDrawTime();
+ EXPECT_EQ(expected_deadline,
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ // Still waiting for surface 2. Once it updates, deadline should trigger
+ // immediately again.
+ SurfaceDamaged(sid2);
+ EXPECT_EQ(base::TimeTicks(),
+ scheduler_.DesiredBeginFrameDeadlineTimeForTest());
+ // Draw and swap now that we aren't throttled.
+ EXPECT_EQ(1, client_.draw_and_swap_count());
+ scheduler_.BeginFrameDeadlineForTest();
+ EXPECT_EQ(2, client_.draw_and_swap_count());
+}
+
+// This test verfies that we try to reschedule the deadline
+// after any event that may change what deadline we want.
+TEST_F(DisplaySchedulerTest, ScheduleBeginFrameDeadline) {
+ SurfaceId root_surface_id(
+ kArbitraryFrameSinkId,
+ LocalSurfaceId(1, base::UnguessableToken::Create()));
+ SurfaceId sid1(kArbitraryFrameSinkId,
+ LocalSurfaceId(2, base::UnguessableToken::Create()));
+ int count = 1;
+ EXPECT_EQ(count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.SetVisible(true);
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.SetVisible(true);
+ EXPECT_EQ(count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.SetVisible(false);
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ // Set the root surface while not visible.
+ scheduler_.SetNewRootSurface(root_surface_id);
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.SetVisible(true);
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ // Set the root surface while visible.
+ scheduler_.SetNewRootSurface(root_surface_id);
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.BeginFrameDeadlineForTest();
+ scheduler_.DidSwapBuffers();
+ AdvanceTimeAndBeginFrameForTest(std::vector<SurfaceId>());
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.DidReceiveSwapBuffersAck();
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.DisplayResized();
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.SetNewRootSurface(root_surface_id);
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ SurfaceDamaged(sid1);
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.SetRootSurfaceResourcesLocked(true);
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+
+ scheduler_.OutputSurfaceLost();
+ EXPECT_EQ(++count, scheduler_.scheduler_begin_frame_deadline_count());
+}
+
+} // namespace
+} // namespace viz
diff --git a/chromium/components/viz/service/display/display_unittest.cc b/chromium/components/viz/service/display/display_unittest.cc
new file mode 100644
index 00000000000..aa68584504c
--- /dev/null
+++ b/chromium/components/viz/service/display/display_unittest.cc
@@ -0,0 +1,658 @@
+// 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 "components/viz/service/display/display.h"
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/test/null_task_runner.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/output/texture_mailbox_deleter.h"
+#include "cc/quads/render_pass.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/surface.h"
+#include "cc/surfaces/surface_manager.h"
+#include "cc/test/compositor_frame_helpers.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/scheduler_test_common.h"
+#include "cc/test/test_shared_bitmap_manager.h"
+#include "components/viz/common/resources/shared_bitmap_manager.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/local_surface_id_allocator.h"
+#include "components/viz/service/display/display_client.h"
+#include "components/viz/service/display/display_scheduler.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::AnyNumber;
+
+namespace viz {
+namespace {
+
+static constexpr FrameSinkId kArbitraryFrameSinkId(3, 3);
+static constexpr FrameSinkId kAnotherFrameSinkId(4, 4);
+
+class TestSoftwareOutputDevice : public cc::SoftwareOutputDevice {
+ public:
+ TestSoftwareOutputDevice() {}
+
+ gfx::Rect damage_rect() const { return damage_rect_; }
+ gfx::Size viewport_pixel_size() const { return viewport_pixel_size_; }
+};
+
+class TestDisplayScheduler : public DisplayScheduler {
+ public:
+ explicit TestDisplayScheduler(cc::BeginFrameSource* begin_frame_source,
+ base::SingleThreadTaskRunner* task_runner)
+ : DisplayScheduler(begin_frame_source, task_runner, 1),
+ damaged(false),
+ display_resized_(false),
+ has_new_root_surface(false),
+ swapped(false) {}
+
+ ~TestDisplayScheduler() override {}
+
+ void DisplayResized() override { display_resized_ = true; }
+
+ void SetNewRootSurface(const SurfaceId& root_surface_id) override {
+ has_new_root_surface = true;
+ }
+
+ void ProcessSurfaceDamage(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack,
+ bool display_damaged) override {
+ if (display_damaged) {
+ damaged = true;
+ needs_draw_ = true;
+ }
+ }
+
+ void DidSwapBuffers() override { swapped = true; }
+
+ void ResetDamageForTest() {
+ damaged = false;
+ display_resized_ = false;
+ has_new_root_surface = false;
+ }
+
+ bool damaged;
+ bool display_resized_;
+ bool has_new_root_surface;
+ bool swapped;
+};
+
+class DisplayTest : public testing::Test {
+ public:
+ DisplayTest()
+ : support_(CompositorFrameSinkSupport::Create(
+ nullptr,
+ &manager_,
+ kArbitraryFrameSinkId,
+ true /* is_root */,
+ true /* handles_frame_sink_id_invalidation */,
+ true /* needs_sync_points */)),
+ task_runner_(new base::NullTaskRunner) {}
+
+ ~DisplayTest() override { support_->EvictCurrentSurface(); }
+
+ void SetUpDisplay(const RendererSettings& settings,
+ std::unique_ptr<cc::TestWebGraphicsContext3D> context) {
+ begin_frame_source_.reset(new cc::StubBeginFrameSource);
+
+ std::unique_ptr<cc::FakeOutputSurface> output_surface;
+ if (context) {
+ auto provider = cc::TestContextProvider::Create(std::move(context));
+ provider->BindToCurrentThread();
+ output_surface = cc::FakeOutputSurface::Create3d(std::move(provider));
+ } else {
+ auto device = base::MakeUnique<TestSoftwareOutputDevice>();
+ software_output_device_ = device.get();
+ output_surface = cc::FakeOutputSurface::CreateSoftware(std::move(device));
+ }
+ output_surface_ = output_surface.get();
+ auto scheduler = base::MakeUnique<TestDisplayScheduler>(
+ begin_frame_source_.get(), task_runner_.get());
+ scheduler_ = scheduler.get();
+ display_ = CreateDisplay(settings, kArbitraryFrameSinkId,
+ std::move(scheduler), std::move(output_surface));
+ manager_.RegisterBeginFrameSource(begin_frame_source_.get(),
+ kArbitraryFrameSinkId);
+ }
+
+ std::unique_ptr<Display> CreateDisplay(
+ const RendererSettings& settings,
+ const FrameSinkId& frame_sink_id,
+ std::unique_ptr<DisplayScheduler> scheduler,
+ std::unique_ptr<cc::OutputSurface> output_surface) {
+ auto display = base::MakeUnique<Display>(
+ &shared_bitmap_manager_, nullptr /* gpu_memory_buffer_manager */,
+ settings, frame_sink_id, std::move(output_surface),
+ std::move(scheduler),
+ base::MakeUnique<cc::TextureMailboxDeleter>(task_runner_.get()));
+ display->SetVisible(true);
+ return display;
+ }
+
+ void TearDownDisplay() {
+ // Only call UnregisterBeginFrameSource if SetupDisplay has been called.
+ if (begin_frame_source_)
+ manager_.UnregisterBeginFrameSource(begin_frame_source_.get());
+ }
+
+ protected:
+ void SubmitCompositorFrame(cc::RenderPassList* pass_list,
+ const LocalSurfaceId& local_surface_id) {
+ cc::CompositorFrame frame = cc::test::MakeCompositorFrame();
+ pass_list->swap(frame.render_pass_list);
+
+ support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
+ }
+
+ FrameSinkManagerImpl manager_;
+ std::unique_ptr<CompositorFrameSinkSupport> support_;
+ LocalSurfaceIdAllocator id_allocator_;
+ scoped_refptr<base::NullTaskRunner> task_runner_;
+ cc::TestSharedBitmapManager shared_bitmap_manager_;
+ std::unique_ptr<cc::BeginFrameSource> begin_frame_source_;
+ std::unique_ptr<Display> display_;
+ TestSoftwareOutputDevice* software_output_device_ = nullptr;
+ cc::FakeOutputSurface* output_surface_ = nullptr;
+ TestDisplayScheduler* scheduler_ = nullptr;
+};
+
+class StubDisplayClient : public DisplayClient {
+ public:
+ void DisplayOutputSurfaceLost() override {}
+ void DisplayWillDrawAndSwap(
+ bool will_draw_and_swap,
+ const cc::RenderPassList& render_passes) override {}
+ void DisplayDidDrawAndSwap() override {}
+};
+
+void CopyCallback(bool* called, std::unique_ptr<cc::CopyOutputResult> result) {
+ *called = true;
+}
+
+// Check that frame is damaged and swapped only under correct conditions.
+TEST_F(DisplayTest, DisplayDamaged) {
+ RendererSettings settings;
+ settings.partial_swap_enabled = true;
+ settings.finish_rendering_on_resize = true;
+ SetUpDisplay(settings, nullptr);
+ gfx::ColorSpace color_space_1 = gfx::ColorSpace::CreateXYZD50();
+ gfx::ColorSpace color_space_2 = gfx::ColorSpace::CreateSCRGBLinear();
+
+ StubDisplayClient client;
+ display_->Initialize(&client, manager_.surface_manager());
+ display_->SetColorSpace(color_space_1, color_space_1);
+
+ LocalSurfaceId local_surface_id(id_allocator_.GenerateId());
+ EXPECT_FALSE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+ display_->SetLocalSurfaceId(local_surface_id, 1.f);
+ EXPECT_FALSE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_TRUE(scheduler_->has_new_root_surface);
+
+ scheduler_->ResetDamageForTest();
+ display_->Resize(gfx::Size(100, 100));
+ EXPECT_FALSE(scheduler_->damaged);
+ EXPECT_TRUE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ // First draw from surface should have full damage.
+ cc::RenderPassList pass_list;
+ auto pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 1, 1);
+ pass->id = 1u;
+ pass_list.push_back(std::move(pass));
+
+ scheduler_->ResetDamageForTest();
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ EXPECT_FALSE(scheduler_->swapped);
+ EXPECT_EQ(0u, output_surface_->num_sent_frames());
+ EXPECT_EQ(gfx::ColorSpace(), output_surface_->last_reshape_color_space());
+ display_->DrawAndSwap();
+ EXPECT_EQ(color_space_1, output_surface_->last_reshape_color_space());
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(1u, output_surface_->num_sent_frames());
+ EXPECT_EQ(gfx::Size(100, 100),
+ software_output_device_->viewport_pixel_size());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100), software_output_device_->damage_rect());
+ {
+ // Only damaged portion should be swapped.
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 1, 1);
+ pass->id = 1u;
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ scheduler_->swapped = false;
+ EXPECT_EQ(color_space_1, output_surface_->last_reshape_color_space());
+ display_->SetColorSpace(color_space_2, color_space_2);
+ display_->DrawAndSwap();
+ EXPECT_EQ(color_space_2, output_surface_->last_reshape_color_space());
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(2u, output_surface_->num_sent_frames());
+ EXPECT_EQ(gfx::Size(100, 100),
+ software_output_device_->viewport_pixel_size());
+ EXPECT_EQ(gfx::Rect(10, 10, 1, 1), software_output_device_->damage_rect());
+ }
+
+ {
+ // Pass has no damage so shouldn't be swapped.
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 0, 0);
+ pass->id = 1u;
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ scheduler_->swapped = false;
+ display_->DrawAndSwap();
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(2u, output_surface_->num_sent_frames());
+ }
+
+ {
+ // Pass is wrong size so shouldn't be swapped.
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 99, 99);
+ pass->damage_rect = gfx::Rect(10, 10, 10, 10);
+ pass->id = 1u;
+
+ local_surface_id = id_allocator_.GenerateId();
+ display_->SetLocalSurfaceId(local_surface_id, 1.f);
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ scheduler_->swapped = false;
+ display_->DrawAndSwap();
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(2u, output_surface_->num_sent_frames());
+ }
+
+ {
+ // Previous frame wasn't swapped, so next swap should have full damage.
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 0, 0);
+ pass->id = 1u;
+
+ local_surface_id = id_allocator_.GenerateId();
+ display_->SetLocalSurfaceId(local_surface_id, 1.f);
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ scheduler_->swapped = false;
+ display_->DrawAndSwap();
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(3u, output_surface_->num_sent_frames());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100),
+ software_output_device_->damage_rect());
+ }
+
+ {
+ // Pass has copy output request so should be swapped.
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 0, 0);
+ bool copy_called = false;
+ pass->copy_requests.push_back(cc::CopyOutputRequest::CreateRequest(
+ base::BindOnce(&CopyCallback, &copy_called)));
+ pass->id = 1u;
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ scheduler_->swapped = false;
+ display_->DrawAndSwap();
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(4u, output_surface_->num_sent_frames());
+ EXPECT_TRUE(copy_called);
+ }
+
+ // Pass has no damage, so shouldn't be swapped, but latency info should be
+ // saved for next swap.
+ {
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 0, 0);
+ pass->id = 1u;
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+
+ cc::CompositorFrame frame = cc::test::MakeCompositorFrame();
+ pass_list.swap(frame.render_pass_list);
+ frame.metadata.latency_info.push_back(ui::LatencyInfo());
+
+ support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ scheduler_->swapped = false;
+ display_->DrawAndSwap();
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(4u, output_surface_->num_sent_frames());
+ }
+
+ // Resize should cause a swap if no frame was swapped at the previous size.
+ {
+ local_surface_id = id_allocator_.GenerateId();
+ display_->SetLocalSurfaceId(local_surface_id, 1.f);
+ scheduler_->swapped = false;
+ display_->Resize(gfx::Size(200, 200));
+ EXPECT_FALSE(scheduler_->swapped);
+ EXPECT_EQ(4u, output_surface_->num_sent_frames());
+
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 200, 200);
+ pass->damage_rect = gfx::Rect(10, 10, 10, 10);
+ pass->id = 1u;
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+
+ cc::CompositorFrame frame = cc::test::MakeCompositorFrame();
+ pass_list.swap(frame.render_pass_list);
+
+ support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ scheduler_->swapped = false;
+ display_->Resize(gfx::Size(100, 100));
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(5u, output_surface_->num_sent_frames());
+
+ // Latency info from previous frame should be sent now.
+ EXPECT_EQ(1u, output_surface_->last_sent_frame()->latency_info.size());
+ }
+
+ {
+ local_surface_id = id_allocator_.GenerateId();
+ display_->SetLocalSurfaceId(local_surface_id, 1.0f);
+ // Surface that's damaged completely should be resized and swapped.
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 99, 99);
+ pass->damage_rect = gfx::Rect(0, 0, 99, 99);
+ pass->id = 1u;
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler_->display_resized_);
+ EXPECT_FALSE(scheduler_->has_new_root_surface);
+
+ scheduler_->swapped = false;
+ display_->DrawAndSwap();
+ EXPECT_TRUE(scheduler_->swapped);
+ EXPECT_EQ(6u, output_surface_->num_sent_frames());
+ EXPECT_EQ(gfx::Size(100, 100),
+ software_output_device_->viewport_pixel_size());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100),
+ software_output_device_->damage_rect());
+ EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
+ }
+ TearDownDisplay();
+}
+
+// Check LatencyInfo storage is cleaned up if it exceeds the limit.
+TEST_F(DisplayTest, MaxLatencyInfoCap) {
+ RendererSettings settings;
+ settings.partial_swap_enabled = true;
+ settings.finish_rendering_on_resize = true;
+ SetUpDisplay(settings, nullptr);
+ gfx::ColorSpace color_space_1 = gfx::ColorSpace::CreateXYZD50();
+ gfx::ColorSpace color_space_2 = gfx::ColorSpace::CreateSCRGBLinear();
+
+ StubDisplayClient client;
+ display_->Initialize(&client, manager_.surface_manager());
+ display_->SetColorSpace(color_space_1, color_space_1);
+
+ LocalSurfaceId local_surface_id(id_allocator_.GenerateId());
+ display_->SetLocalSurfaceId(local_surface_id, 1.f);
+
+ scheduler_->ResetDamageForTest();
+ display_->Resize(gfx::Size(100, 100));
+
+ cc::RenderPassList pass_list;
+ auto pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 1, 1);
+ pass->id = 1u;
+ pass_list.push_back(std::move(pass));
+
+ scheduler_->ResetDamageForTest();
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+
+ display_->DrawAndSwap();
+
+ // This is the same as LatencyInfo::kMaxLatencyInfoNumber.
+ const size_t max_latency_info_count = 100;
+ for (size_t i = 0; i <= max_latency_info_count; ++i) {
+ pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 0, 0);
+ pass->id = 1u;
+
+ pass_list.push_back(std::move(pass));
+ scheduler_->ResetDamageForTest();
+
+ cc::CompositorFrame frame = cc::test::MakeCompositorFrame();
+ pass_list.swap(frame.render_pass_list);
+ frame.metadata.latency_info.push_back(ui::LatencyInfo());
+
+ support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
+
+ display_->DrawAndSwap();
+
+ if (i < max_latency_info_count)
+ EXPECT_EQ(i + 1, display_->stored_latency_info_size_for_testing());
+ else
+ EXPECT_EQ(0u, display_->stored_latency_info_size_for_testing());
+ }
+
+ TearDownDisplay();
+}
+
+class MockedContext : public cc::TestWebGraphicsContext3D {
+ public:
+ MOCK_METHOD0(shallowFinishCHROMIUM, void());
+};
+
+TEST_F(DisplayTest, Finish) {
+ LocalSurfaceId local_surface_id1(id_allocator_.GenerateId());
+ LocalSurfaceId local_surface_id2(id_allocator_.GenerateId());
+
+ RendererSettings settings;
+ settings.partial_swap_enabled = true;
+ settings.finish_rendering_on_resize = true;
+
+ auto context = base::MakeUnique<MockedContext>();
+ MockedContext* context_ptr = context.get();
+ EXPECT_CALL(*context_ptr, shallowFinishCHROMIUM()).Times(0);
+
+ SetUpDisplay(settings, std::move(context));
+
+ StubDisplayClient client;
+ display_->Initialize(&client, manager_.surface_manager());
+
+ display_->SetLocalSurfaceId(local_surface_id1, 1.f);
+
+ display_->Resize(gfx::Size(100, 100));
+
+ {
+ cc::RenderPassList pass_list;
+ auto pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 1, 1);
+ pass->id = 1u;
+ pass_list.push_back(std::move(pass));
+
+ SubmitCompositorFrame(&pass_list, local_surface_id1);
+ }
+
+ display_->DrawAndSwap();
+
+ // First resize and draw shouldn't finish.
+ testing::Mock::VerifyAndClearExpectations(context_ptr);
+
+ EXPECT_CALL(*context_ptr, shallowFinishCHROMIUM());
+ display_->Resize(gfx::Size(150, 150));
+ testing::Mock::VerifyAndClearExpectations(context_ptr);
+
+ // Another resize without a swap doesn't need to finish.
+ EXPECT_CALL(*context_ptr, shallowFinishCHROMIUM()).Times(0);
+ display_->SetLocalSurfaceId(local_surface_id2, 1.f);
+ display_->Resize(gfx::Size(200, 200));
+ testing::Mock::VerifyAndClearExpectations(context_ptr);
+
+ EXPECT_CALL(*context_ptr, shallowFinishCHROMIUM()).Times(0);
+ {
+ cc::RenderPassList pass_list;
+ auto pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 200, 200);
+ pass->damage_rect = gfx::Rect(10, 10, 1, 1);
+ pass->id = 1u;
+ pass_list.push_back(std::move(pass));
+
+ SubmitCompositorFrame(&pass_list, local_surface_id2);
+ }
+
+ display_->DrawAndSwap();
+
+ testing::Mock::VerifyAndClearExpectations(context_ptr);
+
+ EXPECT_CALL(*context_ptr, shallowFinishCHROMIUM());
+ display_->Resize(gfx::Size(250, 250));
+ testing::Mock::VerifyAndClearExpectations(context_ptr);
+ TearDownDisplay();
+}
+
+class CountLossDisplayClient : public StubDisplayClient {
+ public:
+ CountLossDisplayClient() = default;
+
+ void DisplayOutputSurfaceLost() override { ++loss_count_; }
+
+ int loss_count() const { return loss_count_; }
+
+ private:
+ int loss_count_ = 0;
+};
+
+TEST_F(DisplayTest, ContextLossInformsClient) {
+ SetUpDisplay(RendererSettings(), cc::TestWebGraphicsContext3D::Create());
+
+ CountLossDisplayClient client;
+ display_->Initialize(&client, manager_.surface_manager());
+
+ // Verify DidLoseOutputSurface callback is hooked up correctly.
+ EXPECT_EQ(0, client.loss_count());
+ output_surface_->context_provider()->ContextGL()->LoseContextCHROMIUM(
+ GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB);
+ output_surface_->context_provider()->ContextGL()->Flush();
+ EXPECT_EQ(1, client.loss_count());
+ TearDownDisplay();
+}
+
+// Regression test for https://crbug.com/727162: Submitting a CompositorFrame to
+// a surface should only cause damage on the Display the surface belongs to.
+// There should not be a side-effect on other Displays.
+TEST_F(DisplayTest, CompositorFrameDamagesCorrectDisplay) {
+ RendererSettings settings;
+ LocalSurfaceId local_surface_id(id_allocator_.GenerateId());
+
+ // Set up first display.
+ SetUpDisplay(settings, nullptr);
+ StubDisplayClient client;
+ display_->Initialize(&client, manager_.surface_manager());
+ display_->SetLocalSurfaceId(local_surface_id, 1.f);
+
+ // Set up second frame sink + display.
+ auto support2 = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kAnotherFrameSinkId, true /* is_root */,
+ true /* handles_frame_sink_id_invalidation */,
+ true /* needs_sync_points */);
+ auto begin_frame_source2 = base::MakeUnique<cc::StubBeginFrameSource>();
+ auto scheduler_for_display2 = base::MakeUnique<TestDisplayScheduler>(
+ begin_frame_source2.get(), task_runner_.get());
+ TestDisplayScheduler* scheduler2 = scheduler_for_display2.get();
+ auto display2 = CreateDisplay(
+ settings, kAnotherFrameSinkId, std::move(scheduler_for_display2),
+ cc::FakeOutputSurface::CreateSoftware(
+ base::MakeUnique<TestSoftwareOutputDevice>()));
+ manager_.RegisterBeginFrameSource(begin_frame_source2.get(),
+ kAnotherFrameSinkId);
+ StubDisplayClient client2;
+ display2->Initialize(&client2, manager_.surface_manager());
+ display2->SetLocalSurfaceId(local_surface_id, 1.f);
+
+ display_->Resize(gfx::Size(100, 100));
+ display2->Resize(gfx::Size(100, 100));
+
+ scheduler_->ResetDamageForTest();
+ scheduler2->ResetDamageForTest();
+ EXPECT_FALSE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler2->damaged);
+
+ // Submit a frame for display_ with full damage.
+ cc::RenderPassList pass_list;
+ auto pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+ pass->damage_rect = gfx::Rect(10, 10, 1, 1);
+ pass->id = 1;
+ pass_list.push_back(std::move(pass));
+
+ SubmitCompositorFrame(&pass_list, local_surface_id);
+
+ // Should have damaged only display_ but not display2.
+ EXPECT_TRUE(scheduler_->damaged);
+ EXPECT_FALSE(scheduler2->damaged);
+ manager_.UnregisterBeginFrameSource(begin_frame_source2.get());
+ TearDownDisplay();
+}
+
+} // namespace
+} // namespace viz
diff --git a/chromium/components/viz/service/display/surface_aggregator.cc b/chromium/components/viz/service/display/surface_aggregator.cc
new file mode 100644
index 00000000000..0d6cdc7e90c
--- /dev/null
+++ b/chromium/components/viz/service/display/surface_aggregator.cc
@@ -0,0 +1,992 @@
+// Copyright 2014 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/display/surface_aggregator.h"
+
+#include <stddef.h>
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/containers/adapters.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "base/trace_event/trace_event.h"
+#include "cc/base/math_util.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/shared_quad_state.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/surface_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/surfaces/surface.h"
+#include "cc/surfaces/surface_client.h"
+#include "cc/surfaces/surface_manager.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+
+namespace viz {
+namespace {
+
+// Maximum bucket size for the UMA stats.
+constexpr int kUmaStatMaxSurfaces = 30;
+
+const char kUmaValidSurface[] =
+ "Compositing.SurfaceAggregator.SurfaceDrawQuad.ValidSurface";
+const char kUmaMissingSurface[] =
+ "Compositing.SurfaceAggregator.SurfaceDrawQuad.MissingSurface";
+const char kUmaNoActiveFrame[] =
+ "Compositing.SurfaceAggregator.SurfaceDrawQuad.NoActiveFrame";
+
+void MoveMatchingRequests(
+ cc::RenderPassId render_pass_id,
+ std::multimap<cc::RenderPassId, std::unique_ptr<cc::CopyOutputRequest>>*
+ copy_requests,
+ std::vector<std::unique_ptr<cc::CopyOutputRequest>>* output_requests) {
+ auto request_range = copy_requests->equal_range(render_pass_id);
+ for (auto it = request_range.first; it != request_range.second; ++it) {
+ DCHECK(it->second);
+ output_requests->push_back(std::move(it->second));
+ }
+ copy_requests->erase(request_range.first, request_range.second);
+}
+
+// Returns true if the damage rect is valid.
+bool CalculateQuadSpaceDamageRect(
+ const gfx::Transform& quad_to_target_transform,
+ const gfx::Transform& target_to_root_transform,
+ const gfx::Rect& root_damage_rect,
+ gfx::Rect* quad_space_damage_rect) {
+ gfx::Transform quad_to_root_transform(target_to_root_transform,
+ quad_to_target_transform);
+ gfx::Transform inverse_transform(gfx::Transform::kSkipInitialization);
+ bool inverse_valid = quad_to_root_transform.GetInverse(&inverse_transform);
+ if (!inverse_valid)
+ return false;
+
+ *quad_space_damage_rect = cc::MathUtil::ProjectEnclosingClippedRect(
+ inverse_transform, root_damage_rect);
+ return true;
+}
+
+} // namespace
+
+SurfaceAggregator::SurfaceAggregator(cc::SurfaceManager* manager,
+ cc::ResourceProvider* provider,
+ bool aggregate_only_damaged)
+ : manager_(manager),
+ provider_(provider),
+ next_render_pass_id_(1),
+ aggregate_only_damaged_(aggregate_only_damaged),
+ weak_factory_(this) {
+ DCHECK(manager_);
+}
+
+SurfaceAggregator::~SurfaceAggregator() {
+ // Notify client of all surfaces being removed.
+ contained_surfaces_.clear();
+ ProcessAddedAndRemovedSurfaces();
+}
+
+SurfaceAggregator::PrewalkResult::PrewalkResult() {}
+
+SurfaceAggregator::PrewalkResult::~PrewalkResult() {}
+
+// Create a clip rect for an aggregated quad from the original clip rect and
+// the clip rect from the surface it's on.
+SurfaceAggregator::ClipData SurfaceAggregator::CalculateClipRect(
+ const ClipData& surface_clip,
+ const ClipData& quad_clip,
+ const gfx::Transform& target_transform) {
+ ClipData out_clip;
+ if (surface_clip.is_clipped)
+ out_clip = surface_clip;
+
+ if (quad_clip.is_clipped) {
+ // TODO(jamesr): This only works if target_transform maps integer
+ // rects to integer rects.
+ gfx::Rect final_clip =
+ cc::MathUtil::MapEnclosingClippedRect(target_transform, quad_clip.rect);
+ if (out_clip.is_clipped)
+ out_clip.rect.Intersect(final_clip);
+ else
+ out_clip.rect = final_clip;
+ out_clip.is_clipped = true;
+ }
+
+ return out_clip;
+}
+
+cc::RenderPassId SurfaceAggregator::RemapPassId(
+ cc::RenderPassId surface_local_pass_id,
+ const SurfaceId& surface_id) {
+ auto key = std::make_pair(surface_id, surface_local_pass_id);
+ auto it = render_pass_allocator_map_.find(key);
+ if (it != render_pass_allocator_map_.end()) {
+ it->second.in_use = true;
+ return it->second.id;
+ }
+
+ RenderPassInfo render_pass_info;
+ render_pass_info.id = next_render_pass_id_++;
+ render_pass_allocator_map_[key] = render_pass_info;
+ return render_pass_info.id;
+}
+
+int SurfaceAggregator::ChildIdForSurface(cc::Surface* surface) {
+ auto it = surface_id_to_resource_child_id_.find(surface->surface_id());
+ if (it == surface_id_to_resource_child_id_.end()) {
+ int child_id = provider_->CreateChild(
+ base::Bind(&SurfaceAggregator::UnrefResources, surface->client()));
+ provider_->SetChildNeedsSyncTokens(child_id, surface->needs_sync_tokens());
+ surface_id_to_resource_child_id_[surface->surface_id()] = child_id;
+ return child_id;
+ } else {
+ return it->second;
+ }
+}
+
+gfx::Rect SurfaceAggregator::DamageRectForSurface(
+ const cc::Surface* surface,
+ const cc::RenderPass& source,
+ const gfx::Rect& full_rect) const {
+ auto it = previous_contained_surfaces_.find(surface->surface_id());
+ if (it != previous_contained_surfaces_.end()) {
+ int previous_index = it->second;
+ if (previous_index == surface->frame_index())
+ return gfx::Rect();
+ }
+ const SurfaceId& previous_surface_id = surface->previous_frame_surface_id();
+
+ if (surface->surface_id() != previous_surface_id) {
+ it = previous_contained_surfaces_.find(previous_surface_id);
+ }
+ if (it != previous_contained_surfaces_.end()) {
+ int previous_index = it->second;
+ if (previous_index == surface->frame_index() - 1)
+ return source.damage_rect;
+ }
+
+ return full_rect;
+}
+
+// static
+void SurfaceAggregator::UnrefResources(
+ base::WeakPtr<cc::SurfaceClient> surface_client,
+ const std::vector<cc::ReturnedResource>& resources,
+ cc::BlockingTaskRunner* main_thread_task_runner) {
+ if (surface_client)
+ surface_client->UnrefResources(resources);
+}
+
+void SurfaceAggregator::HandleSurfaceQuad(
+ const cc::SurfaceDrawQuad* surface_quad,
+ const gfx::Transform& target_transform,
+ const ClipData& clip_rect,
+ cc::RenderPass* dest_pass,
+ bool ignore_undamaged,
+ gfx::Rect* damage_rect_in_quad_space,
+ bool* damage_rect_in_quad_space_valid) {
+ SurfaceId surface_id = surface_quad->surface_id;
+ // If this surface's id is already in our referenced set then it creates
+ // a cycle in the graph and should be dropped.
+ if (referenced_surfaces_.count(surface_id))
+ return;
+ cc::Surface* surface = manager_->GetSurfaceForId(surface_id);
+ if (!surface || !surface->HasActiveFrame()) {
+ if (surface_quad->fallback_quad) {
+ HandleSurfaceQuad(surface_quad->fallback_quad, target_transform,
+ clip_rect, dest_pass, ignore_undamaged,
+ damage_rect_in_quad_space,
+ damage_rect_in_quad_space_valid);
+ } else if (!surface) {
+ ++uma_stats_.missing_surface;
+ } else {
+ ++uma_stats_.no_active_frame;
+ }
+ return;
+ }
+ ++uma_stats_.valid_surface;
+
+ if (ignore_undamaged) {
+ gfx::Transform quad_to_target_transform(
+ target_transform,
+ surface_quad->shared_quad_state->quad_to_target_transform);
+ *damage_rect_in_quad_space_valid = CalculateQuadSpaceDamageRect(
+ quad_to_target_transform, dest_pass->transform_to_root_target,
+ root_damage_rect_, damage_rect_in_quad_space);
+ if (*damage_rect_in_quad_space_valid &&
+ !damage_rect_in_quad_space->Intersects(surface_quad->visible_rect)) {
+ return;
+ }
+ }
+
+ const cc::CompositorFrame& frame = surface->GetActiveFrame();
+
+ // A map keyed by RenderPass id.
+ cc::Surface::CopyRequestsMap copy_requests;
+ surface->TakeCopyOutputRequests(&copy_requests);
+
+ const cc::RenderPassList& render_pass_list = frame.render_pass_list;
+ if (!valid_surfaces_.count(surface->surface_id())) {
+ for (auto& request : copy_requests)
+ request.second->SendEmptyResult();
+ return;
+ }
+
+ referenced_surfaces_.insert(surface_id);
+ // TODO(vmpstr): provider check is a hack for unittests that don't set up a
+ // resource provider.
+ cc::ResourceProvider::ResourceIdMap empty_map;
+ const auto& child_to_parent_map =
+ provider_ ? provider_->GetChildToParentMap(ChildIdForSurface(surface))
+ : empty_map;
+ bool merge_pass =
+ surface_quad->shared_quad_state->opacity == 1.f && copy_requests.empty();
+
+ const cc::RenderPassList& referenced_passes = render_pass_list;
+ size_t passes_to_copy =
+ merge_pass ? referenced_passes.size() - 1 : referenced_passes.size();
+ for (size_t j = 0; j < passes_to_copy; ++j) {
+ const cc::RenderPass& source = *referenced_passes[j];
+
+ size_t sqs_size = source.shared_quad_state_list.size();
+ size_t dq_size = source.quad_list.size();
+ std::unique_ptr<cc::RenderPass> copy_pass(
+ cc::RenderPass::Create(sqs_size, dq_size));
+
+ cc::RenderPassId remapped_pass_id = RemapPassId(source.id, surface_id);
+
+ copy_pass->SetAll(remapped_pass_id, source.output_rect, source.output_rect,
+ source.transform_to_root_target, source.filters,
+ source.background_filters, blending_color_space_,
+ source.has_transparent_background,
+ source.cache_render_pass,
+ source.has_damage_from_contributing_content);
+
+ MoveMatchingRequests(source.id, &copy_requests, &copy_pass->copy_requests);
+
+ // Contributing passes aggregated in to the pass list need to take the
+ // transform of the surface quad into account to update their transform to
+ // the root surface.
+ copy_pass->transform_to_root_target.ConcatTransform(
+ surface_quad->shared_quad_state->quad_to_target_transform);
+ copy_pass->transform_to_root_target.ConcatTransform(target_transform);
+ copy_pass->transform_to_root_target.ConcatTransform(
+ dest_pass->transform_to_root_target);
+
+ CopyQuadsToPass(source.quad_list, source.shared_quad_state_list,
+ child_to_parent_map, gfx::Transform(), ClipData(),
+ copy_pass.get(), surface_id);
+
+ // If the render pass has copy requests, or should be cached, or has
+ // moving-pixel filters, or in a moving-pixel surface, we should damage the
+ // whole output rect so that we always drawn the full content. Otherwise, we
+ // might have incompleted copy request, or cached patially drawn render
+ // pass.
+ if (!copy_request_passes_.count(remapped_pass_id) &&
+ !copy_pass->cache_render_pass &&
+ !moved_pixel_passes_.count(remapped_pass_id)) {
+ gfx::Transform inverse_transform(gfx::Transform::kSkipInitialization);
+ if (copy_pass->transform_to_root_target.GetInverse(&inverse_transform)) {
+ gfx::Rect damage_rect_in_render_pass_space =
+ cc::MathUtil::ProjectEnclosingClippedRect(inverse_transform,
+ root_damage_rect_);
+ copy_pass->damage_rect.Intersect(damage_rect_in_render_pass_space);
+ }
+ }
+
+ if (copy_pass->has_damage_from_contributing_content)
+ contributing_content_damaged_passes_.insert(copy_pass->id);
+ dest_pass_list_->push_back(std::move(copy_pass));
+ }
+
+ gfx::Transform surface_transform =
+ surface_quad->shared_quad_state->quad_to_target_transform;
+ surface_transform.ConcatTransform(target_transform);
+
+ const auto& last_pass = *render_pass_list.back();
+ // This will check if all the surface_quads (including child surfaces) has
+ // damage because HandleSurfaceQuad is a recursive call by calling
+ // CopyQuadsToPass in it.
+ dest_pass->has_damage_from_contributing_content |=
+ !DamageRectForSurface(surface, last_pass, last_pass.output_rect)
+ .IsEmpty();
+
+ if (merge_pass) {
+ // TODO(jamesr): Clean up last pass special casing.
+ const cc::QuadList& quads = last_pass.quad_list;
+
+ // Intersect the transformed visible rect and the clip rect to create a
+ // smaller cliprect for the quad.
+ ClipData surface_quad_clip_rect(
+ true, cc::MathUtil::MapEnclosingClippedRect(
+ surface_quad->shared_quad_state->quad_to_target_transform,
+ surface_quad->visible_rect));
+ if (surface_quad->shared_quad_state->is_clipped) {
+ surface_quad_clip_rect.rect.Intersect(
+ surface_quad->shared_quad_state->clip_rect);
+ }
+
+ ClipData quads_clip =
+ CalculateClipRect(clip_rect, surface_quad_clip_rect, target_transform);
+
+ CopyQuadsToPass(quads, last_pass.shared_quad_state_list,
+ child_to_parent_map, surface_transform, quads_clip,
+ dest_pass, surface_id);
+ } else {
+ cc::RenderPassId remapped_pass_id = RemapPassId(last_pass.id, surface_id);
+
+ auto* shared_quad_state =
+ CopySharedQuadState(surface_quad->shared_quad_state, target_transform,
+ clip_rect, dest_pass);
+
+ auto* quad = dest_pass->CreateAndAppendDrawQuad<cc::RenderPassDrawQuad>();
+ quad->SetNew(shared_quad_state, surface_quad->rect,
+ surface_quad->visible_rect, remapped_pass_id, 0, gfx::RectF(),
+ gfx::Size(), gfx::Vector2dF(), gfx::PointF(),
+ gfx::RectF(surface_quad->rect));
+ }
+
+ // Need to re-query since referenced_surfaces_ iterators are not stable.
+ referenced_surfaces_.erase(referenced_surfaces_.find(surface_id));
+}
+
+void SurfaceAggregator::AddColorConversionPass() {
+ if (dest_pass_list_->empty())
+ return;
+
+ auto* root_render_pass = dest_pass_list_->back().get();
+ if (root_render_pass->color_space == output_color_space_)
+ return;
+
+ gfx::Rect output_rect = root_render_pass->output_rect;
+ CHECK(root_render_pass->transform_to_root_target == gfx::Transform());
+
+ if (!color_conversion_render_pass_id_)
+ color_conversion_render_pass_id_ = next_render_pass_id_++;
+
+ auto color_conversion_pass = cc::RenderPass::Create(1, 1);
+ color_conversion_pass->SetNew(color_conversion_render_pass_id_, output_rect,
+ root_render_pass->damage_rect,
+ root_render_pass->transform_to_root_target);
+ color_conversion_pass->color_space = output_color_space_;
+
+ auto* shared_quad_state =
+ color_conversion_pass->CreateAndAppendSharedQuadState();
+ shared_quad_state->quad_layer_rect = output_rect;
+ shared_quad_state->visible_quad_layer_rect = output_rect;
+ shared_quad_state->opacity = 1.f;
+
+ auto* quad =
+ color_conversion_pass->CreateAndAppendDrawQuad<cc::RenderPassDrawQuad>();
+ quad->SetNew(shared_quad_state, output_rect, output_rect,
+ root_render_pass->id, 0, gfx::RectF(), gfx::Size(),
+ gfx::Vector2dF(), gfx::PointF(), gfx::RectF(output_rect));
+ dest_pass_list_->push_back(std::move(color_conversion_pass));
+}
+
+cc::SharedQuadState* SurfaceAggregator::CopySharedQuadState(
+ const cc::SharedQuadState* source_sqs,
+ const gfx::Transform& target_transform,
+ const ClipData& clip_rect,
+ cc::RenderPass* dest_render_pass) {
+ auto* copy_shared_quad_state =
+ dest_render_pass->CreateAndAppendSharedQuadState();
+ *copy_shared_quad_state = *source_sqs;
+ // target_transform contains any transformation that may exist
+ // between the context that these quads are being copied from (i.e. the
+ // surface's draw transform when aggregated from within a surface) to the
+ // target space of the pass. This will be identity except when copying the
+ // root draw pass from a surface into a pass when the surface draw quad's
+ // transform is not identity.
+ copy_shared_quad_state->quad_to_target_transform.ConcatTransform(
+ target_transform);
+
+ ClipData new_clip_rect = CalculateClipRect(
+ clip_rect, ClipData(source_sqs->is_clipped, source_sqs->clip_rect),
+ target_transform);
+ copy_shared_quad_state->is_clipped = new_clip_rect.is_clipped;
+ copy_shared_quad_state->clip_rect = new_clip_rect.rect;
+ return copy_shared_quad_state;
+}
+
+void SurfaceAggregator::CopyQuadsToPass(
+ const cc::QuadList& source_quad_list,
+ const cc::SharedQuadStateList& source_shared_quad_state_list,
+ const std::unordered_map<cc::ResourceId, cc::ResourceId>&
+ child_to_parent_map,
+ const gfx::Transform& target_transform,
+ const ClipData& clip_rect,
+ cc::RenderPass* dest_pass,
+ const SurfaceId& surface_id) {
+ const cc::SharedQuadState* last_copied_source_shared_quad_state = nullptr;
+ const cc::SharedQuadState* dest_shared_quad_state = nullptr;
+ // If the current frame has copy requests or cached render passes, then
+ // aggregate the entire thing, as otherwise parts of the copy requests may be
+ // ignored and we could cache partially drawn render pass.
+ const bool ignore_undamaged =
+ aggregate_only_damaged_ && !has_copy_requests_ &&
+ !has_cached_render_passes_ && !moved_pixel_passes_.count(dest_pass->id);
+ // Damage rect in the quad space of the current shared quad state.
+ // TODO(jbauman): This rect may contain unnecessary area if
+ // transform isn't axis-aligned.
+ gfx::Rect damage_rect_in_quad_space;
+ bool damage_rect_in_quad_space_valid = false;
+
+#if DCHECK_IS_ON()
+ // If quads have come in with SharedQuadState out of order, or when quads have
+ // invalid SharedQuadState pointer, it should DCHECK.
+ auto sqs_iter = source_shared_quad_state_list.cbegin();
+ for (auto* quad : source_quad_list) {
+ while (sqs_iter != source_shared_quad_state_list.cend() &&
+ quad->shared_quad_state != *sqs_iter) {
+ ++sqs_iter;
+ }
+ DCHECK(sqs_iter != source_shared_quad_state_list.cend());
+ }
+#endif
+
+ for (auto* quad : source_quad_list) {
+ if (quad->material == cc::DrawQuad::SURFACE_CONTENT) {
+ const auto* surface_quad = cc::SurfaceDrawQuad::MaterialCast(quad);
+ // HandleSurfaceQuad may add other shared quad state, so reset the
+ // current data.
+ last_copied_source_shared_quad_state = nullptr;
+
+ // The primary SurfaceDrawQuad should have already dealt with the fallback
+ // DrawQuad.
+ if (surface_quad->surface_draw_quad_type ==
+ cc::SurfaceDrawQuadType::FALLBACK)
+ continue;
+
+ HandleSurfaceQuad(surface_quad, target_transform, clip_rect, dest_pass,
+ ignore_undamaged, &damage_rect_in_quad_space,
+ &damage_rect_in_quad_space_valid);
+ } else {
+ if (quad->shared_quad_state != last_copied_source_shared_quad_state) {
+ dest_shared_quad_state = CopySharedQuadState(
+ quad->shared_quad_state, target_transform, clip_rect, dest_pass);
+ last_copied_source_shared_quad_state = quad->shared_quad_state;
+ if (aggregate_only_damaged_ && !has_copy_requests_ &&
+ !has_cached_render_passes_) {
+ damage_rect_in_quad_space_valid = CalculateQuadSpaceDamageRect(
+ dest_shared_quad_state->quad_to_target_transform,
+ dest_pass->transform_to_root_target, root_damage_rect_,
+ &damage_rect_in_quad_space);
+ }
+ }
+
+ if (ignore_undamaged) {
+ if (damage_rect_in_quad_space_valid &&
+ !damage_rect_in_quad_space.Intersects(quad->visible_rect))
+ continue;
+ }
+
+ cc::DrawQuad* dest_quad;
+ if (quad->material == cc::DrawQuad::RENDER_PASS) {
+ const auto* pass_quad = cc::RenderPassDrawQuad::MaterialCast(quad);
+ cc::RenderPassId original_pass_id = pass_quad->render_pass_id;
+ cc::RenderPassId remapped_pass_id =
+ RemapPassId(original_pass_id, surface_id);
+
+ // If the RenderPassDrawQuad is referring to other render pass with the
+ // |has_damage_from_contributing_content| set on it, then the dest_pass
+ // should have the flag set on it as well.
+ if (contributing_content_damaged_passes_.count(remapped_pass_id))
+ dest_pass->has_damage_from_contributing_content = true;
+
+ dest_quad = dest_pass->CopyFromAndAppendRenderPassDrawQuad(
+ pass_quad, dest_shared_quad_state, remapped_pass_id);
+ } else if (quad->material == cc::DrawQuad::TEXTURE_CONTENT) {
+ const auto* texture_quad = cc::TextureDrawQuad::MaterialCast(quad);
+ if (texture_quad->secure_output_only &&
+ (!output_is_secure_ || copy_request_passes_.count(dest_pass->id))) {
+ auto* solid_color_quad =
+ dest_pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ solid_color_quad->SetNew(dest_shared_quad_state, quad->rect,
+ quad->visible_rect, SK_ColorBLACK, false);
+ dest_quad = solid_color_quad;
+ } else {
+ dest_quad = dest_pass->CopyFromAndAppendDrawQuad(
+ quad, dest_shared_quad_state);
+ }
+ } else {
+ dest_quad =
+ dest_pass->CopyFromAndAppendDrawQuad(quad, dest_shared_quad_state);
+ }
+ if (!child_to_parent_map.empty()) {
+ for (cc::ResourceId& resource_id : dest_quad->resources) {
+ auto it = child_to_parent_map.find(resource_id);
+ DCHECK(it != child_to_parent_map.end());
+
+ DCHECK_EQ(it->first, resource_id);
+ cc::ResourceId remapped_id = it->second;
+ resource_id = remapped_id;
+ }
+ }
+ }
+ }
+}
+
+void SurfaceAggregator::CopyPasses(const cc::CompositorFrame& frame,
+ cc::Surface* surface) {
+ // The root surface is allowed to have copy output requests, so grab them
+ // off its render passes. This map contains a set of CopyOutputRequests
+ // keyed by each RenderPass id.
+ cc::Surface::CopyRequestsMap copy_requests;
+ surface->TakeCopyOutputRequests(&copy_requests);
+
+ const auto& source_pass_list = frame.render_pass_list;
+ DCHECK(valid_surfaces_.count(surface->surface_id()));
+ if (!valid_surfaces_.count(surface->surface_id()))
+ return;
+
+ // TODO(vmpstr): provider check is a hack for unittests that don't set up a
+ // resource provider.
+ cc::ResourceProvider::ResourceIdMap empty_map;
+ const auto& child_to_parent_map =
+ provider_ ? provider_->GetChildToParentMap(ChildIdForSurface(surface))
+ : empty_map;
+ for (size_t i = 0; i < source_pass_list.size(); ++i) {
+ const auto& source = *source_pass_list[i];
+
+ size_t sqs_size = source.shared_quad_state_list.size();
+ size_t dq_size = source.quad_list.size();
+ auto copy_pass = cc::RenderPass::Create(sqs_size, dq_size);
+
+ MoveMatchingRequests(source.id, &copy_requests, &copy_pass->copy_requests);
+
+ cc::RenderPassId remapped_pass_id =
+ RemapPassId(source.id, surface->surface_id());
+
+ copy_pass->SetAll(remapped_pass_id, source.output_rect, source.output_rect,
+ source.transform_to_root_target, source.filters,
+ source.background_filters, blending_color_space_,
+ source.has_transparent_background,
+ source.cache_render_pass,
+ source.has_damage_from_contributing_content);
+
+ CopyQuadsToPass(source.quad_list, source.shared_quad_state_list,
+ child_to_parent_map, gfx::Transform(), ClipData(),
+ copy_pass.get(), surface->surface_id());
+
+ // If the render pass has copy requests, or should be cached, or has
+ // moving-pixel filters, or in a moving-pixel surface, we should damage the
+ // whole output rect so that we always drawn the full content. Otherwise, we
+ // might have incompleted copy request, or cached patially drawn render
+ // pass.
+ if (!copy_request_passes_.count(remapped_pass_id) &&
+ !copy_pass->cache_render_pass &&
+ !moved_pixel_passes_.count(remapped_pass_id)) {
+ gfx::Transform inverse_transform(gfx::Transform::kSkipInitialization);
+ if (copy_pass->transform_to_root_target.GetInverse(&inverse_transform)) {
+ gfx::Rect damage_rect_in_render_pass_space =
+ cc::MathUtil::ProjectEnclosingClippedRect(inverse_transform,
+ root_damage_rect_);
+ copy_pass->damage_rect.Intersect(damage_rect_in_render_pass_space);
+ }
+ }
+
+ if (copy_pass->has_damage_from_contributing_content)
+ contributing_content_damaged_passes_.insert(copy_pass->id);
+ dest_pass_list_->push_back(std::move(copy_pass));
+ }
+}
+
+void SurfaceAggregator::ProcessAddedAndRemovedSurfaces() {
+ for (const auto& surface : previous_contained_surfaces_) {
+ if (!contained_surfaces_.count(surface.first)) {
+ // Release resources of removed surface.
+ auto it = surface_id_to_resource_child_id_.find(surface.first);
+ if (it != surface_id_to_resource_child_id_.end()) {
+ provider_->DestroyChild(it->second);
+ surface_id_to_resource_child_id_.erase(it);
+ }
+
+ // Notify client of removed surface.
+ cc::Surface* surface_ptr = manager_->GetSurfaceForId(surface.first);
+ if (surface_ptr) {
+ surface_ptr->RunDrawCallback();
+ }
+ }
+ }
+}
+
+// Walk the Surface tree from surface_id. Validate the resources of the current
+// surface and its descendants, check if there are any copy requests, and
+// return the combined damage rect.
+gfx::Rect SurfaceAggregator::PrewalkTree(const SurfaceId& surface_id,
+ bool in_moved_pixel_surface,
+ int parent_pass_id,
+ PrewalkResult* result) {
+ // This is for debugging a possible use after free.
+ // TODO(jbauman): Remove this once we have enough information.
+ // http://crbug.com/560181
+ base::WeakPtr<SurfaceAggregator> debug_weak_this = weak_factory_.GetWeakPtr();
+
+ if (referenced_surfaces_.count(surface_id))
+ return gfx::Rect();
+ cc::Surface* surface = manager_->GetSurfaceForId(surface_id);
+ if (!surface) {
+ contained_surfaces_[surface_id] = 0;
+ return gfx::Rect();
+ }
+ contained_surfaces_[surface_id] = surface->frame_index();
+ if (!surface->HasActiveFrame())
+ return gfx::Rect();
+ const cc::CompositorFrame& frame = surface->GetActiveFrame();
+ int child_id = 0;
+ // TODO(jbauman): hack for unit tests that don't set up rp
+ if (provider_) {
+ child_id = ChildIdForSurface(surface);
+ surface->RefResources(frame.resource_list);
+ provider_->ReceiveFromChild(child_id, frame.resource_list);
+ }
+ CHECK(debug_weak_this.get());
+
+ std::vector<cc::ResourceId> referenced_resources;
+ size_t reserve_size = frame.resource_list.size();
+ referenced_resources.reserve(reserve_size);
+
+ bool invalid_frame = false;
+ cc::ResourceProvider::ResourceIdMap empty_map;
+ const auto& child_to_parent_map =
+ provider_ ? provider_->GetChildToParentMap(child_id) : empty_map;
+
+ CHECK(debug_weak_this.get());
+ cc::RenderPassId remapped_pass_id =
+ RemapPassId(frame.render_pass_list.back()->id, surface_id);
+ if (in_moved_pixel_surface)
+ moved_pixel_passes_.insert(remapped_pass_id);
+ if (parent_pass_id)
+ render_pass_dependencies_[parent_pass_id].insert(remapped_pass_id);
+
+ struct SurfaceInfo {
+ SurfaceInfo(const SurfaceId& id,
+ bool has_moved_pixels,
+ cc::RenderPassId parent_pass_id,
+ const gfx::Transform& target_to_surface_transform)
+ : id(id),
+ has_moved_pixels(has_moved_pixels),
+ parent_pass_id(parent_pass_id),
+ target_to_surface_transform(target_to_surface_transform) {}
+
+ SurfaceId id;
+ bool has_moved_pixels;
+ cc::RenderPassId parent_pass_id;
+ gfx::Transform target_to_surface_transform;
+ };
+ std::vector<SurfaceInfo> child_surfaces;
+
+ gfx::Rect pixel_moving_background_filters_rect;
+ // This data is created once and typically small or empty. Collect all items
+ // and pass to a flat_vector to sort once.
+ std::vector<cc::RenderPassId> pixel_moving_background_filter_passes_data;
+ for (const auto& render_pass : frame.render_pass_list) {
+ if (render_pass->background_filters.HasFilterThatMovesPixels()) {
+ pixel_moving_background_filter_passes_data.push_back(
+ RemapPassId(render_pass->id, surface_id));
+ // TODO(wutao): Partial swap does not work with pixel moving background
+ // filter. See https://crbug.com/737255. Current solution is to mark the
+ // whole output rect as damaged.
+ pixel_moving_background_filters_rect.Union(
+ cc::MathUtil::MapEnclosingClippedRect(
+ render_pass->transform_to_root_target, render_pass->output_rect));
+ }
+ }
+ base::flat_set<cc::RenderPassId> pixel_moving_background_filter_passes(
+ std::move(pixel_moving_background_filter_passes_data),
+ base::KEEP_FIRST_OF_DUPES);
+
+ for (const auto& render_pass : base::Reversed(frame.render_pass_list)) {
+ cc::RenderPassId remapped_pass_id =
+ RemapPassId(render_pass->id, surface_id);
+ bool has_pixel_moving_filter =
+ render_pass->filters.HasFilterThatMovesPixels();
+ if (has_pixel_moving_filter)
+ moved_pixel_passes_.insert(remapped_pass_id);
+ bool in_moved_pixel_pass = has_pixel_moving_filter ||
+ !!moved_pixel_passes_.count(remapped_pass_id);
+ for (auto* quad : render_pass->quad_list) {
+ if (quad->material == cc::DrawQuad::SURFACE_CONTENT) {
+ const auto* surface_quad = cc::SurfaceDrawQuad::MaterialCast(quad);
+ gfx::Transform target_to_surface_transform(
+ render_pass->transform_to_root_target,
+ surface_quad->shared_quad_state->quad_to_target_transform);
+ child_surfaces.emplace_back(surface_quad->surface_id,
+ in_moved_pixel_pass, remapped_pass_id,
+ target_to_surface_transform);
+ } else if (quad->material == cc::DrawQuad::RENDER_PASS) {
+ const auto* render_pass_quad =
+ cc::RenderPassDrawQuad::MaterialCast(quad);
+ if (in_moved_pixel_pass) {
+ moved_pixel_passes_.insert(
+ RemapPassId(render_pass_quad->render_pass_id, surface_id));
+ }
+ if (pixel_moving_background_filter_passes.count(
+ render_pass_quad->render_pass_id)) {
+ in_moved_pixel_pass = true;
+ }
+ render_pass_dependencies_[remapped_pass_id].insert(
+ RemapPassId(render_pass_quad->render_pass_id, surface_id));
+ }
+
+ if (!provider_)
+ continue;
+ for (cc::ResourceId resource_id : quad->resources) {
+ if (!child_to_parent_map.count(resource_id)) {
+ invalid_frame = true;
+ break;
+ }
+ referenced_resources.push_back(resource_id);
+ }
+ }
+ }
+
+ if (invalid_frame)
+ return gfx::Rect();
+ CHECK(debug_weak_this.get());
+ valid_surfaces_.insert(surface->surface_id());
+
+ cc::ResourceIdSet resource_set(std::move(referenced_resources),
+ base::KEEP_FIRST_OF_DUPES);
+ if (provider_)
+ provider_->DeclareUsedResourcesFromChild(child_id, resource_set);
+ CHECK(debug_weak_this.get());
+
+ gfx::Rect damage_rect;
+ gfx::Rect full_damage;
+ cc::RenderPass* last_pass = frame.render_pass_list.back().get();
+ full_damage = last_pass->output_rect;
+ damage_rect =
+ DamageRectForSurface(surface, *last_pass, last_pass->output_rect);
+
+ // Avoid infinite recursion by adding current surface to
+ // referenced_surfaces_.
+ referenced_surfaces_.insert(surface->surface_id());
+ for (const auto& surface_info : child_surfaces) {
+ gfx::Rect surface_damage =
+ PrewalkTree(surface_info.id, surface_info.has_moved_pixels,
+ surface_info.parent_pass_id, result);
+ if (surface_damage.IsEmpty())
+ continue;
+ if (surface_info.has_moved_pixels) {
+ // Areas outside the rect hit by target_to_surface_transform may be
+ // modified if there is a filter that moves pixels.
+ damage_rect = full_damage;
+ continue;
+ }
+
+ damage_rect.Union(cc::MathUtil::MapEnclosingClippedRect(
+ surface_info.target_to_surface_transform, surface_damage));
+ }
+
+ CHECK(debug_weak_this.get());
+ for (const auto& surface_id : frame.metadata.referenced_surfaces) {
+ if (!contained_surfaces_.count(surface_id)) {
+ result->undrawn_surfaces.insert(surface_id);
+ PrewalkTree(surface_id, false, 0, result);
+ }
+ }
+
+ CHECK(debug_weak_this.get());
+ // TODO(staraz): It shouldn't need to call the callback when the damage is
+ // from |surface| and not from |child_surfaces|.
+ if (!damage_rect.IsEmpty()) {
+ surface->RunWillDrawCallback(damage_rect);
+ manager_->SurfaceWillDraw(surface->surface_id());
+ }
+
+ CHECK(debug_weak_this.get());
+ for (const auto& render_pass : frame.render_pass_list) {
+ if (!render_pass->copy_requests.empty()) {
+ cc::RenderPassId remapped_pass_id =
+ RemapPassId(render_pass->id, surface_id);
+ copy_request_passes_.insert(remapped_pass_id);
+ }
+ if (render_pass->cache_render_pass)
+ has_cached_render_passes_ = true;
+ }
+
+ referenced_surfaces_.erase(referenced_surfaces_.find(surface->surface_id()));
+ if (!damage_rect.IsEmpty() && frame.metadata.may_contain_video)
+ result->may_contain_video = true;
+
+ damage_rect.Union(pixel_moving_background_filters_rect);
+ return damage_rect;
+}
+
+void SurfaceAggregator::CopyUndrawnSurfaces(PrewalkResult* prewalk_result) {
+ // undrawn_surfaces are Surfaces that were identified by prewalk as being
+ // referenced by a drawn Surface, but aren't contained in a SurfaceDrawQuad.
+ // They need to be iterated over to ensure that any copy requests on them
+ // (or on Surfaces they reference) are executed.
+ std::vector<SurfaceId> surfaces_to_copy(
+ prewalk_result->undrawn_surfaces.begin(),
+ prewalk_result->undrawn_surfaces.end());
+ DCHECK(referenced_surfaces_.empty());
+
+ for (size_t i = 0; i < surfaces_to_copy.size(); i++) {
+ SurfaceId surface_id = surfaces_to_copy[i];
+ cc::Surface* surface = manager_->GetSurfaceForId(surface_id);
+ if (!surface)
+ continue;
+ if (!surface->HasActiveFrame())
+ continue;
+ const cc::CompositorFrame& frame = surface->GetActiveFrame();
+ bool surface_has_copy_requests = false;
+ for (const auto& render_pass : frame.render_pass_list) {
+ surface_has_copy_requests |= !render_pass->copy_requests.empty();
+ }
+ if (!surface_has_copy_requests) {
+ // Children are not necessarily included in undrawn_surfaces (because
+ // they weren't referenced directly from a drawn surface), but may have
+ // copy requests, so make sure to check them as well.
+ for (const auto& child_id : frame.metadata.referenced_surfaces) {
+ // Don't iterate over the child Surface if it was already listed as a
+ // child of a different Surface, or in the case where there's infinite
+ // recursion.
+ if (!prewalk_result->undrawn_surfaces.count(child_id)) {
+ surfaces_to_copy.push_back(child_id);
+ prewalk_result->undrawn_surfaces.insert(child_id);
+ }
+ }
+ } else {
+ referenced_surfaces_.insert(surface_id);
+ CopyPasses(frame, surface);
+ // CopyPasses may have mutated container, need to re-query to erase.
+ referenced_surfaces_.erase(referenced_surfaces_.find(surface_id));
+ }
+ }
+}
+
+void SurfaceAggregator::PropagateCopyRequestPasses() {
+ std::vector<cc::RenderPassId> copy_requests_to_iterate(
+ copy_request_passes_.begin(), copy_request_passes_.end());
+ while (!copy_requests_to_iterate.empty()) {
+ int first = copy_requests_to_iterate.back();
+ copy_requests_to_iterate.pop_back();
+ auto it = render_pass_dependencies_.find(first);
+ if (it == render_pass_dependencies_.end())
+ continue;
+ for (auto pass : it->second) {
+ if (copy_request_passes_.insert(pass).second) {
+ copy_requests_to_iterate.push_back(pass);
+ }
+ }
+ }
+}
+
+cc::CompositorFrame SurfaceAggregator::Aggregate(const SurfaceId& surface_id) {
+ uma_stats_.Reset();
+
+ cc::Surface* surface = manager_->GetSurfaceForId(surface_id);
+ DCHECK(surface);
+ contained_surfaces_[surface_id] = surface->frame_index();
+
+ if (!surface->HasActiveFrame())
+ return {};
+
+ const cc::CompositorFrame& root_surface_frame = surface->GetActiveFrame();
+ TRACE_EVENT0("viz", "SurfaceAggregator::Aggregate");
+
+ cc::CompositorFrame frame;
+
+ dest_pass_list_ = &frame.render_pass_list;
+
+ valid_surfaces_.clear();
+ has_cached_render_passes_ = false;
+ PrewalkResult prewalk_result;
+ root_damage_rect_ = PrewalkTree(surface_id, false, 0, &prewalk_result);
+ PropagateCopyRequestPasses();
+ has_copy_requests_ = !copy_request_passes_.empty();
+ frame.metadata.may_contain_video = prewalk_result.may_contain_video;
+
+ CopyUndrawnSurfaces(&prewalk_result);
+ referenced_surfaces_.insert(surface_id);
+ CopyPasses(root_surface_frame, surface);
+ // CopyPasses may have mutated container, need to re-query to erase.
+ referenced_surfaces_.erase(referenced_surfaces_.find(surface_id));
+ AddColorConversionPass();
+
+ moved_pixel_passes_.clear();
+ copy_request_passes_.clear();
+ contributing_content_damaged_passes_.clear();
+ render_pass_dependencies_.clear();
+
+ // Remove all render pass mappings that weren't used in the current frame.
+ for (auto it = render_pass_allocator_map_.begin();
+ it != render_pass_allocator_map_.end();) {
+ if (it->second.in_use) {
+ it->second.in_use = false;
+ it++;
+ } else {
+ it = render_pass_allocator_map_.erase(it);
+ }
+ }
+
+ DCHECK(referenced_surfaces_.empty());
+
+ if (dest_pass_list_->empty())
+ return {};
+
+ dest_pass_list_ = NULL;
+ ProcessAddedAndRemovedSurfaces();
+ contained_surfaces_.swap(previous_contained_surfaces_);
+ contained_surfaces_.clear();
+
+ for (auto it : previous_contained_surfaces_) {
+ cc::Surface* surface = manager_->GetSurfaceForId(it.first);
+ if (surface)
+ surface->TakeLatencyInfo(&frame.metadata.latency_info);
+ }
+
+ // TODO(jamesr): Aggregate all resource references into the returned frame's
+ // resource list.
+
+ // Log UMA stats for SurfaceDrawQuads on the number of surfaces that were
+ // aggregated together and any failures.
+ UMA_HISTOGRAM_EXACT_LINEAR(kUmaValidSurface, uma_stats_.valid_surface,
+ kUmaStatMaxSurfaces);
+ UMA_HISTOGRAM_EXACT_LINEAR(kUmaMissingSurface, uma_stats_.missing_surface,
+ kUmaStatMaxSurfaces);
+ UMA_HISTOGRAM_EXACT_LINEAR(kUmaNoActiveFrame, uma_stats_.no_active_frame,
+ kUmaStatMaxSurfaces);
+
+ return frame;
+}
+
+void SurfaceAggregator::ReleaseResources(const SurfaceId& surface_id) {
+ auto it = surface_id_to_resource_child_id_.find(surface_id);
+ if (it != surface_id_to_resource_child_id_.end()) {
+ provider_->DestroyChild(it->second);
+ surface_id_to_resource_child_id_.erase(it);
+ }
+}
+
+void SurfaceAggregator::SetFullDamageForSurface(const SurfaceId& surface_id) {
+ auto it = previous_contained_surfaces_.find(surface_id);
+ if (it == previous_contained_surfaces_.end())
+ return;
+ // Set the last drawn index as 0 to ensure full damage next time it's drawn.
+ it->second = 0;
+}
+
+void SurfaceAggregator::SetOutputColorSpace(
+ const gfx::ColorSpace& blending_color_space,
+ const gfx::ColorSpace& output_color_space) {
+ blending_color_space_ = blending_color_space.IsValid()
+ ? blending_color_space
+ : gfx::ColorSpace::CreateSRGB();
+ output_color_space_ = output_color_space.IsValid()
+ ? output_color_space
+ : gfx::ColorSpace::CreateSRGB();
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display/surface_aggregator.h b/chromium/components/viz/service/display/surface_aggregator.h
new file mode 100644
index 00000000000..f6cff7bde45
--- /dev/null
+++ b/chromium/components/viz/service/display/surface_aggregator.h
@@ -0,0 +1,233 @@
+// Copyright 2014 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_DISPLAY_SURFACE_AGGREGATOR_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_SURFACE_AGGREGATOR_H_
+
+#include <memory>
+#include <unordered_map>
+
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/quads/render_pass.h"
+#include "cc/resources/transferable_resource.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/service/viz_service_export.h"
+#include "ui/gfx/color_space.h"
+
+namespace cc {
+class BlockingTaskRunner;
+class CompositorFrame;
+class ResourceProvider;
+class Surface;
+class SurfaceClient;
+class SurfaceDrawQuad;
+class SurfaceManager;
+} // namespace cc
+
+namespace viz {
+
+class VIZ_SERVICE_EXPORT SurfaceAggregator {
+ public:
+ using SurfaceIndexMap = base::flat_map<SurfaceId, int>;
+
+ SurfaceAggregator(cc::SurfaceManager* manager,
+ cc::ResourceProvider* provider,
+ bool aggregate_only_damaged);
+ ~SurfaceAggregator();
+
+ cc::CompositorFrame Aggregate(const SurfaceId& surface_id);
+ void ReleaseResources(const SurfaceId& surface_id);
+ SurfaceIndexMap& previous_contained_surfaces() {
+ return previous_contained_surfaces_;
+ }
+ void SetFullDamageForSurface(const SurfaceId& surface_id);
+ void set_output_is_secure(bool secure) { output_is_secure_ = secure; }
+
+ // Set the color spaces for the created RenderPasses, which is propagated
+ // to the output surface.
+ void SetOutputColorSpace(const gfx::ColorSpace& blending_color_space,
+ const gfx::ColorSpace& output_color_space);
+
+ private:
+ struct ClipData {
+ ClipData() : is_clipped(false) {}
+ ClipData(bool is_clipped, const gfx::Rect& rect)
+ : is_clipped(is_clipped), rect(rect) {}
+
+ bool is_clipped;
+ gfx::Rect rect;
+ };
+
+ struct PrewalkResult {
+ PrewalkResult();
+ ~PrewalkResult();
+ // This is the set of Surfaces that were referenced by another Surface, but
+ // not included in a SurfaceDrawQuad.
+ base::flat_set<SurfaceId> undrawn_surfaces;
+ bool may_contain_video = false;
+ };
+
+ struct RenderPassInfo {
+ // This is the id the pass is mapped to.
+ int id;
+ // This is true if the pass was used in the last aggregated frame.
+ bool in_use = true;
+ };
+
+ struct SurfaceDrawQuadUmaStats {
+ void Reset() {
+ valid_surface = 0;
+ missing_surface = 0;
+ no_active_frame = 0;
+ }
+
+ // The surface exists and has an active frame.
+ int valid_surface;
+ // The surface doesn't exist.
+ int missing_surface;
+ // The surface exists but doesn't have an active frame.
+ int no_active_frame;
+ };
+
+ ClipData CalculateClipRect(const ClipData& surface_clip,
+ const ClipData& quad_clip,
+ const gfx::Transform& target_transform);
+
+ cc::RenderPassId RemapPassId(cc::RenderPassId surface_local_pass_id,
+ const SurfaceId& surface_id);
+
+ void HandleSurfaceQuad(const cc::SurfaceDrawQuad* surface_quad,
+ const gfx::Transform& target_transform,
+ const ClipData& clip_rect,
+ cc::RenderPass* dest_pass,
+ bool ignore_undamaged,
+ gfx::Rect* damage_rect_in_quad_space,
+ bool* damage_rect_in_quad_space_valid);
+
+ cc::SharedQuadState* CopySharedQuadState(
+ const cc::SharedQuadState* source_sqs,
+ const gfx::Transform& target_transform,
+ const ClipData& clip_rect,
+ cc::RenderPass* dest_render_pass);
+ void CopyQuadsToPass(
+ const cc::QuadList& source_quad_list,
+ const cc::SharedQuadStateList& source_shared_quad_state_list,
+ const std::unordered_map<cc::ResourceId, cc::ResourceId>&
+ resource_to_child_map,
+ const gfx::Transform& target_transform,
+ const ClipData& clip_rect,
+ cc::RenderPass* dest_pass,
+ const SurfaceId& surface_id);
+ gfx::Rect PrewalkTree(const SurfaceId& surface_id,
+ bool in_moved_pixel_surface,
+ int parent_pass,
+ PrewalkResult* result);
+ void CopyUndrawnSurfaces(PrewalkResult* prewalk);
+ void CopyPasses(const cc::CompositorFrame& frame, cc::Surface* surface);
+ void AddColorConversionPass();
+
+ // Remove Surfaces that were referenced before but aren't currently
+ // referenced from the ResourceProvider.
+ // Also notifies SurfaceAggregatorClient of newly added and removed
+ // child surfaces.
+ void ProcessAddedAndRemovedSurfaces();
+
+ void PropagateCopyRequestPasses();
+
+ int ChildIdForSurface(cc::Surface* surface);
+ gfx::Rect DamageRectForSurface(const cc::Surface* surface,
+ const cc::RenderPass& source,
+ const gfx::Rect& full_rect) const;
+
+ static void UnrefResources(base::WeakPtr<cc::SurfaceClient> surface_client,
+ const std::vector<cc::ReturnedResource>& resources,
+ cc::BlockingTaskRunner* main_thread_task_runner);
+
+ cc::SurfaceManager* manager_;
+ cc::ResourceProvider* provider_;
+
+ // Every Surface has its own RenderPass ID namespace. This structure maps
+ // each source (SurfaceId, RenderPass id) to a unified ID namespace that's
+ // used in the aggregated frame. An entry is removed from the map if it's not
+ // used for one output frame.
+ base::flat_map<std::pair<SurfaceId, cc::RenderPassId>, RenderPassInfo>
+ render_pass_allocator_map_;
+ cc::RenderPassId next_render_pass_id_;
+ const bool aggregate_only_damaged_;
+ bool output_is_secure_;
+
+ // The color space for the root render pass. If this is different from
+ // |blending_color_space_|, then a final render pass to convert between
+ // the two will be added. This space must always be valid.
+ gfx::ColorSpace output_color_space_ = gfx::ColorSpace::CreateSRGB();
+ // The color space in which blending is done, used for all non-root render
+ // passes. This space must always be valid.
+ gfx::ColorSpace blending_color_space_ = gfx::ColorSpace::CreateSRGB();
+ // The id for the final color conversion render pass.
+ cc::RenderPassId color_conversion_render_pass_id_ = 0;
+
+ base::flat_map<SurfaceId, int> surface_id_to_resource_child_id_;
+
+ // The following state is only valid for the duration of one Aggregate call
+ // and is only stored on the class to avoid having to pass through every
+ // function call.
+
+ // This is the set of surfaces referenced in the aggregation so far, used to
+ // detect cycles.
+ base::flat_set<SurfaceId> referenced_surfaces_;
+
+ // For each Surface used in the last aggregation, gives the frame_index at
+ // that time.
+ SurfaceIndexMap previous_contained_surfaces_;
+ SurfaceIndexMap contained_surfaces_;
+
+ // After surface validation, every Surface in this set is valid.
+ base::flat_set<SurfaceId> valid_surfaces_;
+
+ // This is the pass list for the aggregated frame.
+ cc::RenderPassList* dest_pass_list_;
+
+ // This is the set of aggregated pass ids that are affected by filters that
+ // move pixels.
+ base::flat_set<cc::RenderPassId> moved_pixel_passes_;
+
+ // This is the set of aggregated pass ids that are drawn by copy requests, so
+ // should not have their damage rects clipped to the root damage rect.
+ base::flat_set<cc::RenderPassId> copy_request_passes_;
+
+ // This is the set of aggregated pass ids that has damage from contributing
+ // content.
+ base::flat_set<cc::RenderPassId> contributing_content_damaged_passes_;
+
+ // This maps each aggregated pass id to the set of (aggregated) pass ids
+ // that its RenderPassDrawQuads depend on
+ base::flat_map<cc::RenderPassId, base::flat_set<cc::RenderPassId>>
+ render_pass_dependencies_;
+
+ // The root damage rect of the currently-aggregating frame.
+ gfx::Rect root_damage_rect_;
+
+ // True if the frame that's currently being aggregated has copy requests.
+ // This is valid during Aggregate after PrewalkTree is called.
+ bool has_copy_requests_;
+
+ // True if the frame that's currently being aggregated has cached render
+ // passes. This is valid during Aggregate after PrewalkTree is called.
+ bool has_cached_render_passes_;
+
+ // Tracks UMA stats for SurfaceDrawQuads during a call to Aggregate().
+ SurfaceDrawQuadUmaStats uma_stats_;
+
+ base::WeakPtrFactory<SurfaceAggregator> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfaceAggregator);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_SURFACE_AGGREGATOR_H_
diff --git a/chromium/components/viz/service/display/surface_aggregator_perftest.cc b/chromium/components/viz/service/display/surface_aggregator_perftest.cc
new file mode 100644
index 00000000000..ab49928a818
--- /dev/null
+++ b/chromium/components/viz/service/display/surface_aggregator_perftest.cc
@@ -0,0 +1,187 @@
+// 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 "base/memory/ptr_util.h"
+#include "cc/base/lap_timer.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/quads/surface_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/surfaces/surface_manager.h"
+#include "cc/test/compositor_frame_helpers.h"
+#include "cc/test/fake_output_surface_client.h"
+#include "cc/test/fake_resource_provider.h"
+#include "cc/test/test_context_provider.h"
+#include "cc/test/test_shared_bitmap_manager.h"
+#include "components/viz/service/display/surface_aggregator.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace viz {
+namespace {
+
+constexpr bool kIsRoot = true;
+constexpr bool kIsChildRoot = false;
+constexpr bool kHandlesFrameSinkIdInvalidation = true;
+constexpr bool kNeedsSyncPoints = true;
+
+const base::UnguessableToken kArbitraryToken = base::UnguessableToken::Create();
+
+class SurfaceAggregatorPerfTest : public testing::Test {
+ public:
+ SurfaceAggregatorPerfTest() {
+ context_provider_ = cc::TestContextProvider::Create();
+ context_provider_->BindToCurrentThread();
+ shared_bitmap_manager_ = base::MakeUnique<cc::TestSharedBitmapManager>();
+
+ resource_provider_ = cc::FakeResourceProvider::Create(
+ context_provider_.get(), shared_bitmap_manager_.get());
+ }
+
+ void RunTest(int num_surfaces,
+ int num_textures,
+ float opacity,
+ bool optimize_damage,
+ bool full_damage,
+ const std::string& name) {
+ std::vector<std::unique_ptr<CompositorFrameSinkSupport>> child_supports(
+ num_surfaces);
+ for (int i = 0; i < num_surfaces; i++) {
+ child_supports[i] = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, FrameSinkId(1, i + 1), kIsChildRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ }
+ aggregator_ = base::MakeUnique<SurfaceAggregator>(
+ manager_.surface_manager(), resource_provider_.get(), optimize_damage);
+ for (int i = 0; i < num_surfaces; i++) {
+ LocalSurfaceId local_surface_id(i + 1, kArbitraryToken);
+
+ auto pass = cc::RenderPass::Create();
+ pass->output_rect = gfx::Rect(0, 0, 1, 2);
+
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+
+ auto* sqs = pass->CreateAndAppendSharedQuadState();
+ for (int j = 0; j < num_textures; j++) {
+ cc::TransferableResource resource;
+ resource.id = j;
+ resource.is_software = true;
+ frame.resource_list.push_back(resource);
+
+ auto* quad = pass->CreateAndAppendDrawQuad<cc::TextureDrawQuad>();
+ const gfx::Rect rect(0, 0, 1, 2);
+ const gfx::Rect opaque_rect;
+ // Half of rects should be visible with partial damage.
+ gfx::Rect visible_rect =
+ j % 2 == 0 ? gfx::Rect(0, 0, 1, 2) : gfx::Rect(0, 1, 1, 1);
+ bool needs_blending = false;
+ bool premultiplied_alpha = false;
+ const gfx::PointF uv_top_left;
+ const gfx::PointF uv_bottom_right;
+ SkColor background_color = SK_ColorGREEN;
+ const float vertex_opacity[4] = {0.f, 0.f, 1.f, 1.f};
+ bool flipped = false;
+ bool nearest_neighbor = false;
+ quad->SetAll(sqs, rect, opaque_rect, visible_rect, needs_blending, j,
+ gfx::Size(), premultiplied_alpha, uv_top_left,
+ uv_bottom_right, background_color, vertex_opacity, flipped,
+ nearest_neighbor, false);
+ }
+ sqs = pass->CreateAndAppendSharedQuadState();
+ sqs->opacity = opacity;
+ if (i >= 1) {
+ auto* surface_quad =
+ pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ surface_quad->SetNew(
+ sqs, gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1),
+ SurfaceId(FrameSinkId(1, i), LocalSurfaceId(i, kArbitraryToken)),
+ cc::SurfaceDrawQuadType::PRIMARY, nullptr);
+ }
+
+ frame.render_pass_list.push_back(std::move(pass));
+ child_supports[i]->SubmitCompositorFrame(local_surface_id,
+ std::move(frame));
+ }
+
+ auto root_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, FrameSinkId(1, num_surfaces + 1), kIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ timer_.Reset();
+ do {
+ auto pass = cc::RenderPass::Create();
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+
+ auto* sqs = pass->CreateAndAppendSharedQuadState();
+ auto* surface_quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ surface_quad->SetNew(
+ sqs, gfx::Rect(0, 0, 100, 100), gfx::Rect(0, 0, 100, 100),
+ SurfaceId(FrameSinkId(1, num_surfaces),
+ LocalSurfaceId(num_surfaces, kArbitraryToken)),
+ cc::SurfaceDrawQuadType::PRIMARY, nullptr);
+
+ pass->output_rect = gfx::Rect(0, 0, 100, 100);
+
+ if (full_damage)
+ pass->damage_rect = gfx::Rect(0, 0, 100, 100);
+ else
+ pass->damage_rect = gfx::Rect(0, 0, 1, 1);
+
+ frame.render_pass_list.push_back(std::move(pass));
+
+ root_support->SubmitCompositorFrame(
+ LocalSurfaceId(num_surfaces + 1, kArbitraryToken), std::move(frame));
+
+ cc::CompositorFrame aggregated = aggregator_->Aggregate(
+ SurfaceId(FrameSinkId(1, num_surfaces + 1),
+ LocalSurfaceId(num_surfaces + 1, kArbitraryToken)));
+ timer_.NextLap();
+ } while (!timer_.HasTimeLimitExpired());
+
+ perf_test::PrintResult("aggregator_speed", "", name, timer_.LapsPerSecond(),
+ "runs/s", true);
+ for (int i = 0; i < num_surfaces; i++)
+ child_supports[i]->EvictCurrentSurface();
+ root_support->EvictCurrentSurface();
+ }
+
+ protected:
+ FrameSinkManagerImpl manager_;
+ scoped_refptr<cc::TestContextProvider> context_provider_;
+ std::unique_ptr<SharedBitmapManager> shared_bitmap_manager_;
+ std::unique_ptr<cc::ResourceProvider> resource_provider_;
+ std::unique_ptr<SurfaceAggregator> aggregator_;
+ cc::LapTimer timer_;
+};
+
+TEST_F(SurfaceAggregatorPerfTest, ManySurfacesOpaque) {
+ RunTest(20, 100, 1.f, false, true, "many_surfaces_opaque");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, ManySurfacesTransparent) {
+ RunTest(20, 100, .5f, false, true, "many_surfaces_transparent");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, FewSurfaces) {
+ RunTest(3, 1000, 1.f, false, true, "few_surfaces");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, ManySurfacesOpaqueDamageCalc) {
+ RunTest(20, 100, 1.f, true, true, "many_surfaces_opaque_damage_calc");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, ManySurfacesTransparentDamageCalc) {
+ RunTest(20, 100, .5f, true, true, "many_surfaces_transparent_damage_calc");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, FewSurfacesDamageCalc) {
+ RunTest(3, 1000, 1.f, true, true, "few_surfaces_damage_calc");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, FewSurfacesAggregateDamaged) {
+ RunTest(3, 1000, 1.f, true, false, "few_surfaces_aggregate_damaged");
+}
+
+} // namespace
+} // namespace viz
diff --git a/chromium/components/viz/service/display/surface_aggregator_pixeltest.cc b/chromium/components/viz/service/display/surface_aggregator_pixeltest.cc
new file mode 100644
index 00000000000..91c06d9956f
--- /dev/null
+++ b/chromium/components/viz/service/display/surface_aggregator_pixeltest.cc
@@ -0,0 +1,319 @@
+// Copyright 2014 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 "build/build_config.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/surface_draw_quad.h"
+#include "cc/surfaces/surface.h"
+#include "cc/surfaces/surface_manager.h"
+#include "cc/test/compositor_frame_helpers.h"
+#include "cc/test/pixel_comparator.h"
+#include "cc/test/pixel_test.h"
+#include "components/viz/common/surfaces/local_surface_id_allocator.h"
+#include "components/viz/service/display/surface_aggregator.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if !defined(OS_ANDROID)
+
+namespace viz {
+namespace {
+
+constexpr FrameSinkId kArbitraryRootFrameSinkId(1, 1);
+constexpr FrameSinkId kArbitraryChildFrameSinkId(2, 2);
+constexpr FrameSinkId kArbitraryLeftFrameSinkId(3, 3);
+constexpr FrameSinkId kArbitraryRightFrameSinkId(4, 4);
+constexpr bool kIsRoot = true;
+constexpr bool kIsChildRoot = false;
+constexpr bool kHandlesFrameSinkIdInvalidation = true;
+constexpr bool kNeedsSyncPoints = true;
+
+class SurfaceAggregatorPixelTest
+ : public cc::RendererPixelTest<cc::GLRenderer> {
+ public:
+ SurfaceAggregatorPixelTest()
+ : support_(
+ CompositorFrameSinkSupport::Create(nullptr,
+ &manager_,
+ kArbitraryRootFrameSinkId,
+ kIsRoot,
+ kHandlesFrameSinkIdInvalidation,
+ kNeedsSyncPoints)) {}
+ ~SurfaceAggregatorPixelTest() override { support_->EvictCurrentSurface(); }
+
+ protected:
+ FrameSinkManagerImpl manager_;
+ LocalSurfaceIdAllocator allocator_;
+ std::unique_ptr<CompositorFrameSinkSupport> support_;
+};
+
+cc::SharedQuadState* CreateAndAppendTestSharedQuadState(
+ cc::RenderPass* render_pass,
+ const gfx::Transform& transform,
+ const gfx::Size& size) {
+ const gfx::Rect layer_rect = gfx::Rect(size);
+ const gfx::Rect visible_layer_rect = gfx::Rect(size);
+ const gfx::Rect clip_rect = gfx::Rect(size);
+ bool is_clipped = false;
+ float opacity = 1.f;
+ const SkBlendMode blend_mode = SkBlendMode::kSrcOver;
+ auto* shared_state = render_pass->CreateAndAppendSharedQuadState();
+ shared_state->SetAll(transform, layer_rect, visible_layer_rect, clip_rect,
+ is_clipped, opacity, blend_mode, 0);
+ return shared_state;
+}
+
+// Draws a very simple frame with no surface references.
+TEST_F(SurfaceAggregatorPixelTest, DrawSimpleFrame) {
+ gfx::Rect rect(device_viewport_size_);
+ int id = 1;
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(id, rect, rect, gfx::Transform());
+
+ CreateAndAppendTestSharedQuadState(pass.get(), gfx::Transform(),
+ device_viewport_size_);
+
+ auto* color_quad = pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ bool force_anti_aliasing_off = false;
+ color_quad->SetNew(pass->shared_quad_state_list.back(), rect, rect,
+ SK_ColorGREEN, force_anti_aliasing_off);
+
+ auto root_frame = cc::test::MakeCompositorFrame();
+ root_frame.render_pass_list.push_back(std::move(pass));
+
+ LocalSurfaceId root_local_surface_id = allocator_.GenerateId();
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id);
+ support_->SubmitCompositorFrame(root_local_surface_id, std::move(root_frame));
+
+ SurfaceAggregator aggregator(manager_.surface_manager(),
+ resource_provider_.get(), true);
+ cc::CompositorFrame aggregated_frame = aggregator.Aggregate(root_surface_id);
+
+ bool discard_alpha = false;
+ cc::ExactPixelComparator pixel_comparator(discard_alpha);
+ cc::RenderPassList* pass_list = &aggregated_frame.render_pass_list;
+ EXPECT_TRUE(RunPixelTest(pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green.png")),
+ pixel_comparator));
+}
+
+// Draws a frame with simple surface embedding.
+TEST_F(SurfaceAggregatorPixelTest, DrawSimpleAggregatedFrame) {
+ gfx::Size child_size(200, 100);
+ std::unique_ptr<CompositorFrameSinkSupport> child_support =
+ CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryChildFrameSinkId, kIsChildRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support->frame_sink_id(),
+ child_local_surface_id);
+ LocalSurfaceId root_local_surface_id = allocator_.GenerateId();
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id);
+
+ {
+ gfx::Rect rect(device_viewport_size_);
+ int id = 1;
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(id, rect, rect, gfx::Transform());
+
+ CreateAndAppendTestSharedQuadState(pass.get(), gfx::Transform(),
+ device_viewport_size_);
+
+ auto* surface_quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ surface_quad->SetNew(pass->shared_quad_state_list.back(),
+ gfx::Rect(child_size), gfx::Rect(child_size),
+ child_surface_id, cc::SurfaceDrawQuadType::PRIMARY,
+ nullptr);
+
+ auto* color_quad = pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ bool force_anti_aliasing_off = false;
+ color_quad->SetNew(pass->shared_quad_state_list.back(), rect, rect,
+ SK_ColorYELLOW, force_anti_aliasing_off);
+
+ auto root_frame = cc::test::MakeCompositorFrame();
+ root_frame.render_pass_list.push_back(std::move(pass));
+
+ support_->SubmitCompositorFrame(root_local_surface_id,
+ std::move(root_frame));
+ }
+
+ {
+ gfx::Rect rect(child_size);
+ int id = 1;
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(id, rect, rect, gfx::Transform());
+
+ CreateAndAppendTestSharedQuadState(pass.get(), gfx::Transform(),
+ child_size);
+
+ auto* color_quad = pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ bool force_anti_aliasing_off = false;
+ color_quad->SetNew(pass->shared_quad_state_list.back(), rect, rect,
+ SK_ColorBLUE, force_anti_aliasing_off);
+
+ auto child_frame = cc::test::MakeCompositorFrame();
+ child_frame.render_pass_list.push_back(std::move(pass));
+
+ child_support->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+ }
+
+ SurfaceAggregator aggregator(manager_.surface_manager(),
+ resource_provider_.get(), true);
+ cc::CompositorFrame aggregated_frame = aggregator.Aggregate(root_surface_id);
+
+ bool discard_alpha = false;
+ cc::ExactPixelComparator pixel_comparator(discard_alpha);
+ cc::RenderPassList* pass_list = &aggregated_frame.render_pass_list;
+ EXPECT_TRUE(RunPixelTest(pass_list,
+ base::FilePath(FILE_PATH_LITERAL("blue_yellow.png")),
+ pixel_comparator));
+
+ child_support->EvictCurrentSurface();
+}
+
+// Tests a surface quad that has a non-identity transform into its pass.
+TEST_F(SurfaceAggregatorPixelTest, DrawAggregatedFrameWithSurfaceTransforms) {
+ gfx::Size child_size(100, 200);
+ gfx::Size quad_size(100, 100);
+ // Structure:
+ // root (200x200) -> left_child (100x200 @ 0x0,
+ // right_child (100x200 @ 0x100)
+ // left_child -> top_green_quad (100x100 @ 0x0),
+ // bottom_blue_quad (100x100 @ 0x100)
+ // right_child -> top_blue_quad (100x100 @ 0x0),
+ // bottom_green_quad (100x100 @ 0x100)
+ std::unique_ptr<CompositorFrameSinkSupport> left_support =
+ CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryLeftFrameSinkId, kIsChildRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ std::unique_ptr<CompositorFrameSinkSupport> right_support =
+ CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryRightFrameSinkId, kIsChildRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId left_child_local_id = allocator_.GenerateId();
+ SurfaceId left_child_id(left_support->frame_sink_id(), left_child_local_id);
+ LocalSurfaceId right_child_local_id = allocator_.GenerateId();
+ SurfaceId right_child_id(right_support->frame_sink_id(),
+ right_child_local_id);
+ LocalSurfaceId root_local_surface_id = allocator_.GenerateId();
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id);
+
+ {
+ gfx::Rect rect(device_viewport_size_);
+ int id = 1;
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(id, rect, rect, gfx::Transform());
+
+ gfx::Transform surface_transform;
+ CreateAndAppendTestSharedQuadState(pass.get(), surface_transform,
+ device_viewport_size_);
+
+ auto* left_surface_quad =
+ pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ left_surface_quad->SetNew(pass->shared_quad_state_list.back(),
+ gfx::Rect(child_size), gfx::Rect(child_size),
+ left_child_id, cc::SurfaceDrawQuadType::PRIMARY,
+ nullptr);
+
+ surface_transform.Translate(100, 0);
+ CreateAndAppendTestSharedQuadState(pass.get(), surface_transform,
+ device_viewport_size_);
+
+ auto* right_surface_quad =
+ pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ right_surface_quad->SetNew(pass->shared_quad_state_list.back(),
+ gfx::Rect(child_size), gfx::Rect(child_size),
+ right_child_id, cc::SurfaceDrawQuadType::PRIMARY,
+ nullptr);
+
+ auto root_frame = cc::test::MakeCompositorFrame();
+ root_frame.render_pass_list.push_back(std::move(pass));
+
+ support_->SubmitCompositorFrame(root_local_surface_id,
+ std::move(root_frame));
+ }
+
+ {
+ gfx::Rect rect(child_size);
+ int id = 1;
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(id, rect, rect, gfx::Transform());
+
+ CreateAndAppendTestSharedQuadState(pass.get(), gfx::Transform(),
+ child_size);
+
+ auto* top_color_quad =
+ pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ bool force_anti_aliasing_off = false;
+ top_color_quad->SetNew(pass->shared_quad_state_list.back(),
+ gfx::Rect(quad_size), gfx::Rect(quad_size),
+ SK_ColorGREEN, force_anti_aliasing_off);
+
+ auto* bottom_color_quad =
+ pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ bottom_color_quad->SetNew(
+ pass->shared_quad_state_list.back(), gfx::Rect(0, 100, 100, 100),
+ gfx::Rect(0, 100, 100, 100), SK_ColorBLUE, force_anti_aliasing_off);
+
+ auto child_frame = cc::test::MakeCompositorFrame();
+ child_frame.render_pass_list.push_back(std::move(pass));
+
+ left_support->SubmitCompositorFrame(left_child_local_id,
+ std::move(child_frame));
+ }
+
+ {
+ gfx::Rect rect(child_size);
+ int id = 1;
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(id, rect, rect, gfx::Transform());
+
+ CreateAndAppendTestSharedQuadState(pass.get(), gfx::Transform(),
+ child_size);
+
+ auto* top_color_quad =
+ pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ bool force_anti_aliasing_off = false;
+ top_color_quad->SetNew(pass->shared_quad_state_list.back(),
+ gfx::Rect(quad_size), gfx::Rect(quad_size),
+ SK_ColorBLUE, force_anti_aliasing_off);
+
+ auto* bottom_color_quad =
+ pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ bottom_color_quad->SetNew(
+ pass->shared_quad_state_list.back(), gfx::Rect(0, 100, 100, 100),
+ gfx::Rect(0, 100, 100, 100), SK_ColorGREEN, force_anti_aliasing_off);
+
+ auto child_frame = cc::test::MakeCompositorFrame();
+ child_frame.render_pass_list.push_back(std::move(pass));
+
+ right_support->SubmitCompositorFrame(right_child_local_id,
+ std::move(child_frame));
+ }
+
+ SurfaceAggregator aggregator(manager_.surface_manager(),
+ resource_provider_.get(), true);
+ cc::CompositorFrame aggregated_frame = aggregator.Aggregate(root_surface_id);
+
+ bool discard_alpha = false;
+ cc::ExactPixelComparator pixel_comparator(discard_alpha);
+ cc::RenderPassList* pass_list = &aggregated_frame.render_pass_list;
+ EXPECT_TRUE(RunPixelTest(
+ pass_list,
+ base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
+ pixel_comparator));
+
+ left_support->EvictCurrentSurface();
+ right_support->EvictCurrentSurface();
+}
+
+} // namespace
+} // namespace viz
+
+#endif // !defined(OS_ANDROID)
diff --git a/chromium/components/viz/service/display/surface_aggregator_unittest.cc b/chromium/components/viz/service/display/surface_aggregator_unittest.cc
new file mode 100644
index 00000000000..78f35d4de81
--- /dev/null
+++ b/chromium/components/viz/service/display/surface_aggregator_unittest.cc
@@ -0,0 +1,3095 @@
+
+// Copyright 2014 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/display/surface_aggregator.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/stringprintf.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/surface_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/surfaces/surface.h"
+#include "cc/surfaces/surface_manager.h"
+#include "cc/test/compositor_frame_helpers.h"
+#include "cc/test/fake_compositor_frame_sink_support_client.h"
+#include "cc/test/fake_resource_provider.h"
+#include "cc/test/fake_surface_observer.h"
+#include "cc/test/render_pass_test_utils.h"
+#include "cc/test/test_shared_bitmap_manager.h"
+#include "components/viz/common/resources/shared_bitmap_manager.h"
+#include "components/viz/common/surfaces/local_surface_id_allocator.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace viz {
+namespace {
+
+constexpr FrameSinkId kArbitraryRootFrameSinkId(1, 1);
+constexpr FrameSinkId kArbitraryFrameSinkId1(2, 2);
+constexpr FrameSinkId kArbitraryFrameSinkId2(3, 3);
+constexpr FrameSinkId kArbitraryMiddleFrameSinkId(4, 4);
+constexpr FrameSinkId kArbitraryReservedFrameSinkId(5, 5);
+constexpr FrameSinkId kArbitraryFrameSinkId3(6, 6);
+const base::UnguessableToken kArbitraryToken = base::UnguessableToken::Create();
+constexpr bool kRootIsRoot = true;
+constexpr bool kChildIsRoot = false;
+constexpr bool kHandlesFrameSinkIdInvalidation = true;
+constexpr bool kNeedsSyncPoints = false;
+
+SurfaceId InvalidSurfaceId() {
+ static SurfaceId invalid(FrameSinkId(),
+ LocalSurfaceId(0xdeadbeef, kArbitraryToken));
+ return invalid;
+}
+
+gfx::Size SurfaceSize() {
+ static gfx::Size size(100, 100);
+ return size;
+}
+
+class SurfaceAggregatorTest : public testing::Test {
+ public:
+ explicit SurfaceAggregatorTest(bool use_damage_rect)
+ : observer_(false),
+ support_(
+ CompositorFrameSinkSupport::Create(&fake_client_,
+ &manager_,
+ kArbitraryRootFrameSinkId,
+ kRootIsRoot,
+ kHandlesFrameSinkIdInvalidation,
+ kNeedsSyncPoints)),
+ aggregator_(manager_.surface_manager(), NULL, use_damage_rect) {
+ manager_.surface_manager()->AddObserver(&observer_);
+ }
+
+ SurfaceAggregatorTest() : SurfaceAggregatorTest(false) {}
+
+ void TearDown() override {
+ observer_.Reset();
+ support_->EvictCurrentSurface();
+ testing::Test::TearDown();
+ }
+
+ struct Quad {
+ static Quad SolidColorQuad(SkColor color) {
+ Quad quad;
+ quad.material = cc::DrawQuad::SOLID_COLOR;
+ quad.color = color;
+ return quad;
+ }
+
+ static Quad SurfaceQuad(const SurfaceId& primary_surface_id,
+ const SurfaceId& fallback_surface_id,
+ float opacity) {
+ Quad quad;
+ quad.material = cc::DrawQuad::SURFACE_CONTENT;
+ quad.opacity = opacity;
+ quad.primary_surface_id = primary_surface_id;
+ quad.fallback_surface_id = fallback_surface_id;
+ return quad;
+ }
+
+ static Quad RenderPassQuad(int id) {
+ Quad quad;
+ quad.material = cc::DrawQuad::RENDER_PASS;
+ quad.render_pass_id = id;
+ return quad;
+ }
+
+ cc::DrawQuad::Material material;
+ // Set when material==DrawQuad::SURFACE_CONTENT.
+ SurfaceId primary_surface_id;
+ SurfaceId fallback_surface_id;
+ float opacity;
+ // Set when material==DrawQuad::SOLID_COLOR.
+ SkColor color;
+ // Set when material==DrawQuad::RENDER_PASS.
+ cc::RenderPassId render_pass_id;
+
+ private:
+ Quad()
+ : material(cc::DrawQuad::INVALID), opacity(1.f), color(SK_ColorWHITE) {}
+ };
+
+ struct Pass {
+ Pass(Quad* quads, size_t quad_count, int id)
+ : quads(quads), quad_count(quad_count), id(id) {}
+ Pass(Quad* quads, size_t quad_count)
+ : quads(quads), quad_count(quad_count) {}
+
+ Quad* quads;
+ size_t quad_count;
+ int id = 1;
+ };
+
+ static void AddQuadInPass(cc::RenderPass* pass, Quad desc) {
+ switch (desc.material) {
+ case cc::DrawQuad::SOLID_COLOR:
+ AddQuad(pass, gfx::Rect(0, 0, 5, 5), desc.color);
+ break;
+ case cc::DrawQuad::SURFACE_CONTENT:
+ AddSurfaceQuad(pass, gfx::Size(5, 5), desc.opacity,
+ desc.primary_surface_id, desc.fallback_surface_id);
+ break;
+ case cc::DrawQuad::RENDER_PASS:
+ AddRenderPassQuad(pass, desc.render_pass_id);
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ static void AddPasses(cc::RenderPassList* pass_list,
+ const gfx::Rect& output_rect,
+ Pass* passes,
+ size_t pass_count) {
+ gfx::Transform root_transform;
+ for (size_t i = 0; i < pass_count; ++i) {
+ Pass pass = passes[i];
+ cc::RenderPass* test_pass =
+ AddRenderPass(pass_list, pass.id, output_rect, root_transform,
+ cc::FilterOperations());
+ for (size_t j = 0; j < pass.quad_count; ++j) {
+ AddQuadInPass(test_pass, pass.quads[j]);
+ }
+ }
+ }
+
+ static void TestQuadMatchesExpectations(Quad expected_quad,
+ const cc::DrawQuad* quad) {
+ switch (expected_quad.material) {
+ case cc::DrawQuad::SOLID_COLOR: {
+ ASSERT_EQ(cc::DrawQuad::SOLID_COLOR, quad->material);
+
+ const auto* solid_color_quad =
+ cc::SolidColorDrawQuad::MaterialCast(quad);
+
+ EXPECT_EQ(expected_quad.color, solid_color_quad->color);
+ break;
+ }
+ case cc::DrawQuad::RENDER_PASS: {
+ ASSERT_EQ(cc::DrawQuad::RENDER_PASS, quad->material);
+
+ const auto* render_pass_quad =
+ cc::RenderPassDrawQuad::MaterialCast(quad);
+
+ EXPECT_EQ(expected_quad.render_pass_id,
+ render_pass_quad->render_pass_id);
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ static void TestPassMatchesExpectations(Pass expected_pass,
+ const cc::RenderPass* pass) {
+ ASSERT_EQ(expected_pass.quad_count, pass->quad_list.size());
+ for (auto iter = pass->quad_list.cbegin(); iter != pass->quad_list.cend();
+ ++iter) {
+ SCOPED_TRACE(base::StringPrintf("Quad number %" PRIuS, iter.index()));
+ TestQuadMatchesExpectations(expected_pass.quads[iter.index()], *iter);
+ }
+ }
+
+ static void TestPassesMatchExpectations(Pass* expected_passes,
+ size_t expected_pass_count,
+ const cc::RenderPassList* passes) {
+ ASSERT_EQ(expected_pass_count, passes->size());
+
+ for (size_t i = 0; i < passes->size(); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Pass number %" PRIuS, i));
+ cc::RenderPass* pass = (*passes)[i].get();
+ TestPassMatchesExpectations(expected_passes[i], pass);
+ }
+ }
+
+ private:
+ static void AddSurfaceQuad(cc::RenderPass* pass,
+ const gfx::Size& surface_size,
+ float opacity,
+ const SurfaceId& primary_surface_id,
+ const SurfaceId& fallback_surface_id) {
+ gfx::Transform layer_to_target_transform;
+ gfx::Size layer_bounds = surface_size;
+ gfx::Rect visible_layer_rect = gfx::Rect(surface_size);
+ gfx::Rect clip_rect = gfx::Rect(surface_size);
+ bool is_clipped = false;
+ SkBlendMode blend_mode = SkBlendMode::kSrcOver;
+
+ auto* shared_quad_state = pass->CreateAndAppendSharedQuadState();
+ shared_quad_state->SetAll(layer_to_target_transform,
+ gfx::Rect(layer_bounds), visible_layer_rect,
+ clip_rect, is_clipped, opacity, blend_mode, 0);
+
+ cc::SurfaceDrawQuad* surface_quad =
+ pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ cc::SurfaceDrawQuad* fallback_surface_quad = nullptr;
+ if (fallback_surface_id.is_valid())
+ fallback_surface_quad =
+ pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+
+ gfx::Rect quad_rect = gfx::Rect(surface_size);
+ surface_quad->SetNew(pass->shared_quad_state_list.back(), quad_rect,
+ quad_rect, primary_surface_id,
+ cc::SurfaceDrawQuadType::PRIMARY,
+ fallback_surface_quad);
+
+ if (fallback_surface_quad) {
+ fallback_surface_quad->SetNew(pass->shared_quad_state_list.back(),
+ quad_rect, quad_rect, fallback_surface_id,
+ cc::SurfaceDrawQuadType::FALLBACK, nullptr);
+ }
+ }
+
+ static void AddRenderPassQuad(cc::RenderPass* pass,
+ cc::RenderPassId render_pass_id) {
+ gfx::Rect output_rect = gfx::Rect(0, 0, 5, 5);
+ auto* shared_state = pass->CreateAndAppendSharedQuadState();
+ shared_state->SetAll(gfx::Transform(), output_rect, output_rect,
+ output_rect, false, 1, SkBlendMode::kSrcOver, 0);
+ auto* quad = pass->CreateAndAppendDrawQuad<cc::RenderPassDrawQuad>();
+ quad->SetNew(shared_state, output_rect, output_rect, render_pass_id, 0,
+ gfx::RectF(), gfx::Size(), gfx::Vector2dF(), gfx::PointF(),
+ gfx::RectF());
+ }
+
+ protected:
+ FrameSinkManagerImpl manager_;
+ cc::FakeSurfaceObserver observer_;
+ cc::FakeCompositorFrameSinkSupportClient fake_client_;
+ std::unique_ptr<CompositorFrameSinkSupport> support_;
+ SurfaceAggregator aggregator_;
+};
+
+class SurfaceAggregatorValidSurfaceTest : public SurfaceAggregatorTest {
+ public:
+ explicit SurfaceAggregatorValidSurfaceTest(bool use_damage_rect)
+ : SurfaceAggregatorTest(use_damage_rect),
+ child_support_(
+ CompositorFrameSinkSupport::Create(nullptr,
+ &manager_,
+ kArbitraryReservedFrameSinkId,
+ kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation,
+ kNeedsSyncPoints)) {}
+ SurfaceAggregatorValidSurfaceTest()
+ : SurfaceAggregatorValidSurfaceTest(false) {}
+
+ void SetUp() override {
+ SurfaceAggregatorTest::SetUp();
+ root_local_surface_id_ = allocator_.GenerateId();
+ root_surface_ = manager_.surface_manager()->GetSurfaceForId(
+ SurfaceId(support_->frame_sink_id(), root_local_surface_id_));
+ }
+
+ void TearDown() override {
+ child_support_->EvictCurrentSurface();
+ SurfaceAggregatorTest::TearDown();
+ }
+
+ void AggregateAndVerify(Pass* expected_passes,
+ size_t expected_pass_count,
+ SurfaceId* surface_ids,
+ size_t expected_surface_count) {
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(
+ SurfaceId(support_->frame_sink_id(), root_local_surface_id_));
+
+ TestPassesMatchExpectations(expected_passes, expected_pass_count,
+ &aggregated_frame.render_pass_list);
+
+ // Ensure no duplicate pass ids output.
+ std::set<cc::RenderPassId> used_passes;
+ for (const auto& pass : aggregated_frame.render_pass_list) {
+ EXPECT_TRUE(used_passes.insert(pass->id).second);
+ }
+
+ EXPECT_EQ(expected_surface_count,
+ aggregator_.previous_contained_surfaces().size());
+ for (size_t i = 0; i < expected_surface_count; i++) {
+ EXPECT_TRUE(
+ aggregator_.previous_contained_surfaces().find(surface_ids[i]) !=
+ aggregator_.previous_contained_surfaces().end());
+ }
+ }
+
+ void SubmitPassListAsFrame(CompositorFrameSinkSupport* support,
+ const LocalSurfaceId& local_surface_id,
+ cc::RenderPassList* pass_list) {
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+ pass_list->swap(frame.render_pass_list);
+
+ support->SubmitCompositorFrame(local_surface_id, std::move(frame));
+ }
+
+ void SubmitCompositorFrame(CompositorFrameSinkSupport* support,
+ Pass* passes,
+ size_t pass_count,
+ const LocalSurfaceId& local_surface_id) {
+ cc::RenderPassList pass_list;
+ AddPasses(&pass_list, gfx::Rect(SurfaceSize()), passes, pass_count);
+ SubmitPassListAsFrame(support, local_surface_id, &pass_list);
+ }
+
+ void QueuePassAsFrame(std::unique_ptr<cc::RenderPass> pass,
+ const LocalSurfaceId& local_surface_id,
+ CompositorFrameSinkSupport* support) {
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ child_frame.render_pass_list.push_back(std::move(pass));
+
+ support->SubmitCompositorFrame(local_surface_id, std::move(child_frame));
+ }
+
+ protected:
+ LocalSurfaceId root_local_surface_id_;
+ cc::Surface* root_surface_;
+ LocalSurfaceIdAllocator allocator_;
+ std::unique_ptr<CompositorFrameSinkSupport> child_support_;
+ LocalSurfaceIdAllocator child_allocator_;
+};
+
+// Tests that a very simple frame containing only two solid color quads makes it
+// through the aggregator correctly.
+TEST_F(SurfaceAggregatorValidSurfaceTest, SimpleFrame) {
+ Quad quads[] = {Quad::SolidColorQuad(SK_ColorRED),
+ Quad::SolidColorQuad(SK_ColorBLUE)};
+ Pass passes[] = {Pass(quads, arraysize(quads))};
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ SurfaceId ids[] = {root_surface_id};
+
+ AggregateAndVerify(passes, arraysize(passes), ids, arraysize(ids));
+
+ // Check that WillDrawSurface was called.
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), fake_client_.last_damage_rect());
+ EXPECT_EQ(root_local_surface_id_, fake_client_.last_local_surface_id());
+
+ // Check that SurfaceObserver::OnSurfaceWillDraw was called.
+ EXPECT_TRUE(observer_.SurfaceWillDrawCalled(root_surface_id));
+}
+
+TEST_F(SurfaceAggregatorValidSurfaceTest, OpacityCopied) {
+ auto embedded_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId1, kRootIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId embedded_local_surface_id = allocator_.GenerateId();
+ SurfaceId embedded_surface_id(embedded_support->frame_sink_id(),
+ embedded_local_surface_id);
+
+ Quad embedded_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SolidColorQuad(SK_ColorBLUE)};
+ Pass embedded_passes[] = {Pass(embedded_quads, arraysize(embedded_quads))};
+
+ SubmitCompositorFrame(embedded_support.get(), embedded_passes,
+ arraysize(embedded_passes), embedded_local_surface_id);
+
+ Quad quads[] = {
+ Quad::SurfaceQuad(embedded_surface_id, InvalidSurfaceId(), .5f)};
+ Pass passes[] = {Pass(quads, arraysize(quads))};
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ auto& render_pass_list = aggregated_frame.render_pass_list;
+ ASSERT_EQ(2u, render_pass_list.size());
+ auto& shared_quad_state_list = render_pass_list[0]->shared_quad_state_list;
+ ASSERT_EQ(2u, shared_quad_state_list.size());
+ EXPECT_EQ(1.f, shared_quad_state_list.ElementAt(0)->opacity);
+ EXPECT_EQ(1.f, shared_quad_state_list.ElementAt(1)->opacity);
+
+ auto& shared_quad_state_list2 = render_pass_list[1]->shared_quad_state_list;
+ ASSERT_EQ(1u, shared_quad_state_list2.size());
+ EXPECT_EQ(.5f, shared_quad_state_list2.ElementAt(0)->opacity);
+
+ embedded_support->EvictCurrentSurface();
+}
+
+TEST_F(SurfaceAggregatorValidSurfaceTest, MultiPassSimpleFrame) {
+ Quad quads[][2] = {{Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SolidColorQuad(SK_ColorLTGRAY)},
+ {Quad::SolidColorQuad(SK_ColorGRAY),
+ Quad::SolidColorQuad(SK_ColorDKGRAY)}};
+ Pass passes[] = {Pass(quads[0], arraysize(quads[0]), 1),
+ Pass(quads[1], arraysize(quads[1]), 2)};
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ SurfaceId ids[] = {root_surface_id};
+
+ AggregateAndVerify(passes, arraysize(passes), ids, arraysize(ids));
+}
+
+// Ensure that the render pass ID map properly keeps and deletes entries.
+TEST_F(SurfaceAggregatorValidSurfaceTest, MultiPassDeallocation) {
+ Quad quads[][2] = {{Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SolidColorQuad(SK_ColorLTGRAY)},
+ {Quad::SolidColorQuad(SK_ColorGRAY),
+ Quad::SolidColorQuad(SK_ColorDKGRAY)}};
+ Pass passes[] = {Pass(quads[0], arraysize(quads[0]), 2),
+ Pass(quads[1], arraysize(quads[1]), 1)};
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ SurfaceId surface_id(support_->frame_sink_id(), root_local_surface_id_);
+
+ cc::CompositorFrame aggregated_frame;
+ aggregated_frame = aggregator_.Aggregate(surface_id);
+ auto id0 = aggregated_frame.render_pass_list[0]->id;
+ auto id1 = aggregated_frame.render_pass_list[1]->id;
+ EXPECT_NE(id1, id0);
+
+ // Aggregated cc::RenderPass ids should remain the same between frames.
+ aggregated_frame = aggregator_.Aggregate(surface_id);
+ EXPECT_EQ(id0, aggregated_frame.render_pass_list[0]->id);
+ EXPECT_EQ(id1, aggregated_frame.render_pass_list[1]->id);
+
+ Pass passes2[] = {Pass(quads[0], arraysize(quads[0]), 3),
+ Pass(quads[1], arraysize(quads[1]), 1)};
+
+ SubmitCompositorFrame(support_.get(), passes2, arraysize(passes2),
+ root_local_surface_id_);
+
+ // The cc::RenderPass that still exists should keep the same ID.
+ aggregated_frame = aggregator_.Aggregate(surface_id);
+ auto id2 = aggregated_frame.render_pass_list[0]->id;
+ EXPECT_NE(id2, id1);
+ EXPECT_NE(id2, id0);
+ EXPECT_EQ(id1, aggregated_frame.render_pass_list[1]->id);
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ // |id1| didn't exist in the previous frame, so it should be
+ // mapped to a new ID.
+ aggregated_frame = aggregator_.Aggregate(surface_id);
+ auto id3 = aggregated_frame.render_pass_list[0]->id;
+ EXPECT_NE(id3, id2);
+ EXPECT_NE(id3, id1);
+ EXPECT_NE(id3, id0);
+ EXPECT_EQ(id1, aggregated_frame.render_pass_list[1]->id);
+}
+
+// This tests very simple embedding. root_surface has a frame containing a few
+// solid color quads and a surface quad referencing embedded_surface.
+// embedded_surface has a frame containing only a solid color quad. The solid
+// color quad should be aggregated into the final frame.
+TEST_F(SurfaceAggregatorValidSurfaceTest, SimpleSurfaceReference) {
+ auto embedded_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId1, kRootIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId embedded_local_surface_id = allocator_.GenerateId();
+ SurfaceId embedded_surface_id(embedded_support->frame_sink_id(),
+ embedded_local_surface_id);
+
+ Quad embedded_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN)};
+ Pass embedded_passes[] = {Pass(embedded_quads, arraysize(embedded_quads))};
+
+ SubmitCompositorFrame(embedded_support.get(), embedded_passes,
+ arraysize(embedded_passes), embedded_local_surface_id);
+
+ Quad root_quads[] = {
+ Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SurfaceQuad(embedded_surface_id, InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorBLACK)};
+ Pass root_passes[] = {Pass(root_quads, arraysize(root_quads))};
+
+ SubmitCompositorFrame(support_.get(), root_passes, arraysize(root_passes),
+ root_local_surface_id_);
+
+ Quad expected_quads[] = {Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SolidColorQuad(SK_ColorBLACK)};
+ Pass expected_passes[] = {Pass(expected_quads, arraysize(expected_quads))};
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ SurfaceId ids[] = {root_surface_id, embedded_surface_id};
+ AggregateAndVerify(expected_passes, arraysize(expected_passes), ids,
+ arraysize(ids));
+
+ embedded_support->EvictCurrentSurface();
+}
+
+// This test verifies that in the absence of a primary Surface,
+// SurfaceAggregator will embed a fallback Surface, if available. If the primary
+// Surface is available, though, the fallback will not be used.
+TEST_F(SurfaceAggregatorValidSurfaceTest, FallbackSurfaceReference) {
+ auto primary_child_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId1, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId primary_child_local_surface_id = allocator_.GenerateId();
+ SurfaceId primary_child_surface_id(primary_child_support->frame_sink_id(),
+ primary_child_local_surface_id);
+
+ auto fallback_child_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId2, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId fallback_child_local_surface_id = allocator_.GenerateId();
+ SurfaceId fallback_child_surface_id(fallback_child_support->frame_sink_id(),
+ fallback_child_local_surface_id);
+
+ Quad fallback_child_quads[] = {Quad::SolidColorQuad(SK_ColorRED)};
+ Pass fallback_child_passes[] = {
+ Pass(fallback_child_quads, arraysize(fallback_child_quads))};
+
+ // Submit a cc::CompositorFrame to the fallback Surface containing a red
+ // SolidColorDrawQuad.
+ SubmitCompositorFrame(fallback_child_support.get(), fallback_child_passes,
+ arraysize(fallback_child_passes),
+ fallback_child_local_surface_id);
+
+ // Try to embed |primary_child_surface_id| and if unavailabe, embed
+ // |fallback_child_surface_id|.
+ Quad root_quads[] = {Quad::SurfaceQuad(primary_child_surface_id,
+ fallback_child_surface_id, 1.f)};
+ Pass root_passes[] = {Pass(root_quads, arraysize(root_quads))};
+
+ SubmitCompositorFrame(support_.get(), root_passes, arraysize(root_passes),
+ root_local_surface_id_);
+
+ // There is no cc::CompositorFrame submitted to |primary_child_surface_id| and
+ // so |fallback_child_surface_id| will be embedded and we should see a red
+ // SolidColorDrawQuad.
+ Quad expected_quads1[] = {Quad::SolidColorQuad(SK_ColorRED)};
+ Pass expected_passes1[] = {Pass(expected_quads1, arraysize(expected_quads1))};
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ SurfaceId ids[] = {root_surface_id, primary_child_surface_id,
+ fallback_child_surface_id};
+ AggregateAndVerify(expected_passes1, arraysize(expected_passes1), ids,
+ arraysize(ids));
+
+ // Check that SurfaceObserver::OnSurfaceWillDraw was called only
+ // for the fallback surface.
+ EXPECT_FALSE(observer_.SurfaceWillDrawCalled(primary_child_surface_id));
+ EXPECT_TRUE(observer_.SurfaceWillDrawCalled(fallback_child_surface_id));
+
+ observer_.Reset();
+
+ Quad primary_child_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN)};
+ Pass primary_child_passes[] = {
+ Pass(primary_child_quads, arraysize(primary_child_quads))};
+
+ // Submit a cc::CompositorFrame to the primary Surface containing a green
+ // SolidColorDrawQuad.
+ SubmitCompositorFrame(primary_child_support.get(), primary_child_passes,
+ arraysize(primary_child_passes),
+ primary_child_local_surface_id);
+
+ // Now that the primary Surface has a cc::CompositorFrame, we expect
+ // SurfaceAggregator to embed the primary Surface, and drop the fallback
+ // Surface.
+ Quad expected_quads2[] = {Quad::SolidColorQuad(SK_ColorGREEN)};
+ Pass expected_passes2[] = {Pass(expected_quads2, arraysize(expected_quads2))};
+ AggregateAndVerify(expected_passes2, arraysize(expected_passes2), ids,
+ arraysize(ids));
+
+ // Check that SurfaceObserver::OnSurfaceWillDraw was called only
+ // for the primary surface.
+ EXPECT_TRUE(observer_.SurfaceWillDrawCalled(primary_child_surface_id));
+ EXPECT_FALSE(observer_.SurfaceWillDrawCalled(fallback_child_surface_id));
+
+ primary_child_support->EvictCurrentSurface();
+ fallback_child_support->EvictCurrentSurface();
+}
+
+// This test verifies that in the presence of both primary Surface and fallback
+// Surface, the fallback will not be used.
+TEST_F(SurfaceAggregatorValidSurfaceTest, FallbackSurfaceReferenceWithPrimary) {
+ auto primary_child_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId1, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId primary_child_local_surface_id = allocator_.GenerateId();
+ SurfaceId primary_child_surface_id(primary_child_support->frame_sink_id(),
+ primary_child_local_surface_id);
+ Quad primary_child_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN)};
+ Pass primary_child_passes[] = {
+ Pass(primary_child_quads, arraysize(primary_child_quads))};
+
+ // Submit a cc::CompositorFrame to the primary Surface containing a green
+ // SolidColorDrawQuad.
+ SubmitCompositorFrame(primary_child_support.get(), primary_child_passes,
+ arraysize(primary_child_passes),
+ primary_child_local_surface_id);
+
+ auto fallback_child_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId2, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId fallback_child_local_surface_id = allocator_.GenerateId();
+ SurfaceId fallback_child_surface_id(fallback_child_support->frame_sink_id(),
+ fallback_child_local_surface_id);
+
+ Quad fallback_child_quads[] = {Quad::SolidColorQuad(SK_ColorRED)};
+ Pass fallback_child_passes[] = {
+ Pass(fallback_child_quads, arraysize(fallback_child_quads))};
+
+ // Submit a cc::CompositorFrame to the fallback Surface containing a red
+ // SolidColorDrawQuad.
+ SubmitCompositorFrame(fallback_child_support.get(), fallback_child_passes,
+ arraysize(fallback_child_passes),
+ fallback_child_local_surface_id);
+
+ // Try to embed |primary_child_surface_id| and if unavailabe, embed
+ // |fallback_child_surface_id|.
+ Quad root_quads[] = {Quad::SurfaceQuad(primary_child_surface_id,
+ fallback_child_surface_id, 1.f)};
+ Pass root_passes[] = {Pass(root_quads, arraysize(root_quads))};
+
+ SubmitCompositorFrame(support_.get(), root_passes, arraysize(root_passes),
+ root_local_surface_id_);
+
+ // The cc::CompositorFrame is submitted to |primary_child_surface_id|, so
+ // |fallback_child_surface_id| will not be used and we should see a green
+ // SolidColorDrawQuad.
+ Quad expected_quads1[] = {Quad::SolidColorQuad(SK_ColorGREEN)};
+ Pass expected_passes1[] = {Pass(expected_quads1, arraysize(expected_quads1))};
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ SurfaceId ids[] = {root_surface_id, primary_child_surface_id,
+ fallback_child_surface_id};
+ AggregateAndVerify(expected_passes1, arraysize(expected_passes1), ids,
+ arraysize(ids));
+
+ primary_child_support->EvictCurrentSurface();
+ fallback_child_support->EvictCurrentSurface();
+}
+
+TEST_F(SurfaceAggregatorValidSurfaceTest, CopyRequest) {
+ auto embedded_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId1, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId embedded_local_surface_id = allocator_.GenerateId();
+ SurfaceId embedded_surface_id(embedded_support->frame_sink_id(),
+ embedded_local_surface_id);
+
+ Quad embedded_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN)};
+ Pass embedded_passes[] = {Pass(embedded_quads, arraysize(embedded_quads))};
+
+ SubmitCompositorFrame(embedded_support.get(), embedded_passes,
+ arraysize(embedded_passes), embedded_local_surface_id);
+ auto copy_request = cc::CopyOutputRequest::CreateEmptyRequest();
+ auto* copy_request_ptr = copy_request.get();
+ embedded_support->RequestCopyOfSurface(std::move(copy_request));
+
+ Quad root_quads[] = {
+ Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SurfaceQuad(embedded_surface_id, InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorBLACK)};
+ Pass root_passes[] = {Pass(root_quads, arraysize(root_quads))};
+
+ SubmitCompositorFrame(support_.get(), root_passes, arraysize(root_passes),
+ root_local_surface_id_);
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ Quad expected_quads[] = {
+ Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::RenderPassQuad(aggregated_frame.render_pass_list[0]->id),
+ Quad::SolidColorQuad(SK_ColorBLACK)};
+ Pass expected_passes[] = {Pass(embedded_quads, arraysize(embedded_quads)),
+ Pass(expected_quads, arraysize(expected_quads))};
+ TestPassesMatchExpectations(expected_passes, arraysize(expected_passes),
+ &aggregated_frame.render_pass_list);
+ ASSERT_EQ(2u, aggregated_frame.render_pass_list.size());
+ ASSERT_EQ(1u, aggregated_frame.render_pass_list[0]->copy_requests.size());
+ DCHECK_EQ(copy_request_ptr,
+ aggregated_frame.render_pass_list[0]->copy_requests[0].get());
+
+ SurfaceId surface_ids[] = {root_surface_id, embedded_surface_id};
+ EXPECT_EQ(arraysize(surface_ids),
+ aggregator_.previous_contained_surfaces().size());
+ for (size_t i = 0; i < arraysize(surface_ids); i++) {
+ EXPECT_TRUE(
+ aggregator_.previous_contained_surfaces().find(surface_ids[i]) !=
+ aggregator_.previous_contained_surfaces().end());
+ }
+
+ embedded_support->EvictCurrentSurface();
+}
+
+// Root surface may contain copy requests.
+TEST_F(SurfaceAggregatorValidSurfaceTest, RootCopyRequest) {
+ auto embedded_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId2, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId embedded_local_surface_id = allocator_.GenerateId();
+ SurfaceId embedded_surface_id(embedded_support->frame_sink_id(),
+ embedded_local_surface_id);
+
+ Quad embedded_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN)};
+ Pass embedded_passes[] = {Pass(embedded_quads, arraysize(embedded_quads))};
+
+ SubmitCompositorFrame(embedded_support.get(), embedded_passes,
+ arraysize(embedded_passes), embedded_local_surface_id);
+ auto copy_request(cc::CopyOutputRequest::CreateEmptyRequest());
+ auto* copy_request_ptr = copy_request.get();
+ auto copy_request2(cc::CopyOutputRequest::CreateEmptyRequest());
+ auto* copy_request2_ptr = copy_request2.get();
+
+ Quad root_quads[] = {
+ Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SurfaceQuad(embedded_surface_id, InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorBLACK)};
+ Quad root_quads2[] = {Quad::SolidColorQuad(SK_ColorRED)};
+ Pass root_passes[] = {Pass(root_quads, arraysize(root_quads), 1),
+ Pass(root_quads2, arraysize(root_quads2), 2)};
+ {
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+ frame.render_pass_list[0]->copy_requests.push_back(std::move(copy_request));
+ frame.render_pass_list[1]->copy_requests.push_back(
+ std::move(copy_request2));
+
+ support_->SubmitCompositorFrame(root_local_surface_id_, std::move(frame));
+ }
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ Quad expected_quads[] = {Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SolidColorQuad(SK_ColorBLACK)};
+ Pass expected_passes[] = {Pass(expected_quads, arraysize(expected_quads)),
+ Pass(root_quads2, arraysize(root_quads2))};
+ TestPassesMatchExpectations(expected_passes, arraysize(expected_passes),
+ &aggregated_frame.render_pass_list);
+ ASSERT_EQ(2u, aggregated_frame.render_pass_list.size());
+ ASSERT_EQ(1u, aggregated_frame.render_pass_list[0]->copy_requests.size());
+ DCHECK_EQ(copy_request_ptr,
+ aggregated_frame.render_pass_list[0]->copy_requests[0].get());
+ ASSERT_EQ(1u, aggregated_frame.render_pass_list[1]->copy_requests.size());
+ DCHECK_EQ(copy_request2_ptr,
+ aggregated_frame.render_pass_list[1]->copy_requests[0].get());
+
+ SurfaceId surface_ids[] = {root_surface_id, embedded_surface_id};
+ EXPECT_EQ(arraysize(surface_ids),
+ aggregator_.previous_contained_surfaces().size());
+ for (size_t i = 0; i < arraysize(surface_ids); i++) {
+ EXPECT_TRUE(
+ aggregator_.previous_contained_surfaces().find(surface_ids[i]) !=
+ aggregator_.previous_contained_surfaces().end());
+ }
+
+ // Ensure copy requests have been removed from root surface.
+ const cc::CompositorFrame& original_frame =
+ manager_.surface_manager()
+ ->GetSurfaceForId(root_surface_id)
+ ->GetActiveFrame();
+ const auto& original_pass_list = original_frame.render_pass_list;
+ ASSERT_EQ(2u, original_pass_list.size());
+ DCHECK(original_pass_list[0]->copy_requests.empty());
+ DCHECK(original_pass_list[1]->copy_requests.empty());
+
+ embedded_support->EvictCurrentSurface();
+}
+
+TEST_F(SurfaceAggregatorValidSurfaceTest, UnreferencedSurface) {
+ auto embedded_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId1, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ auto parent_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId2, kRootIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId embedded_local_surface_id = allocator_.GenerateId();
+ SurfaceId embedded_surface_id(embedded_support->frame_sink_id(),
+ embedded_local_surface_id);
+ SurfaceId nonexistent_surface_id(support_->frame_sink_id(),
+ allocator_.GenerateId());
+
+ Quad embedded_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN)};
+ Pass embedded_passes[] = {Pass(embedded_quads, arraysize(embedded_quads))};
+
+ SubmitCompositorFrame(embedded_support.get(), embedded_passes,
+ arraysize(embedded_passes), embedded_local_surface_id);
+ auto copy_request(cc::CopyOutputRequest::CreateEmptyRequest());
+ auto* copy_request_ptr = copy_request.get();
+ embedded_support->RequestCopyOfSurface(std::move(copy_request));
+
+ LocalSurfaceId parent_local_surface_id = allocator_.GenerateId();
+ SurfaceId parent_surface_id(parent_support->frame_sink_id(),
+ parent_local_surface_id);
+
+ Quad parent_quads[] = {
+ Quad::SolidColorQuad(SK_ColorGRAY),
+ Quad::SurfaceQuad(embedded_surface_id, InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorLTGRAY)};
+ Pass parent_passes[] = {Pass(parent_quads, arraysize(parent_quads))};
+
+ {
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+
+ AddPasses(&frame.render_pass_list, gfx::Rect(SurfaceSize()), parent_passes,
+ arraysize(parent_passes));
+
+ frame.metadata.referenced_surfaces.push_back(embedded_surface_id);
+
+ parent_support->SubmitCompositorFrame(parent_local_surface_id,
+ std::move(frame));
+ }
+
+ Quad root_quads[] = {Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SolidColorQuad(SK_ColorBLACK)};
+ Pass root_passes[] = {Pass(root_quads, arraysize(root_quads))};
+
+ {
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ frame.metadata.referenced_surfaces.push_back(parent_surface_id);
+ // Reference to Surface ID of a Surface that doesn't exist should be
+ // included in previous_contained_surfaces, but otherwise ignored.
+ frame.metadata.referenced_surfaces.push_back(nonexistent_surface_id);
+
+ support_->SubmitCompositorFrame(root_local_surface_id_, std::move(frame));
+ }
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ // First pass should come from surface that had a copy request but was not
+ // referenced directly. The second pass comes from the root surface.
+ // parent_quad should be ignored because it is neither referenced through a
+ // SurfaceDrawQuad nor has a copy request on it.
+ Pass expected_passes[] = {Pass(embedded_quads, arraysize(embedded_quads)),
+ Pass(root_quads, arraysize(root_quads))};
+ TestPassesMatchExpectations(expected_passes, arraysize(expected_passes),
+ &aggregated_frame.render_pass_list);
+ ASSERT_EQ(2u, aggregated_frame.render_pass_list.size());
+ ASSERT_EQ(1u, aggregated_frame.render_pass_list[0]->copy_requests.size());
+ DCHECK_EQ(copy_request_ptr,
+ aggregated_frame.render_pass_list[0]->copy_requests[0].get());
+
+ SurfaceId surface_ids[] = {
+ SurfaceId(support_->frame_sink_id(), root_local_surface_id_),
+ parent_surface_id, embedded_surface_id, nonexistent_surface_id};
+ EXPECT_EQ(arraysize(surface_ids),
+ aggregator_.previous_contained_surfaces().size());
+ for (size_t i = 0; i < arraysize(surface_ids); i++) {
+ EXPECT_TRUE(
+ aggregator_.previous_contained_surfaces().find(surface_ids[i]) !=
+ aggregator_.previous_contained_surfaces().end());
+ }
+
+ embedded_support->EvictCurrentSurface();
+ parent_support->EvictCurrentSurface();
+}
+
+// This tests referencing a surface that has multiple render passes.
+TEST_F(SurfaceAggregatorValidSurfaceTest, MultiPassSurfaceReference) {
+ LocalSurfaceId embedded_local_surface_id = child_allocator_.GenerateId();
+ SurfaceId embedded_surface_id(child_support_->frame_sink_id(),
+ embedded_local_surface_id);
+
+ int pass_ids[] = {1, 2, 3};
+
+ Quad embedded_quads[][2] = {
+ {Quad::SolidColorQuad(1), Quad::SolidColorQuad(2)},
+ {Quad::SolidColorQuad(3), Quad::RenderPassQuad(pass_ids[0])},
+ {Quad::SolidColorQuad(4), Quad::RenderPassQuad(pass_ids[1])}};
+ Pass embedded_passes[] = {
+ Pass(embedded_quads[0], arraysize(embedded_quads[0]), pass_ids[0]),
+ Pass(embedded_quads[1], arraysize(embedded_quads[1]), pass_ids[1]),
+ Pass(embedded_quads[2], arraysize(embedded_quads[2]), pass_ids[2])};
+
+ SubmitCompositorFrame(child_support_.get(), embedded_passes,
+ arraysize(embedded_passes), embedded_local_surface_id);
+
+ Quad root_quads[][2] = {
+ {Quad::SolidColorQuad(5), Quad::SolidColorQuad(6)},
+ {Quad::SurfaceQuad(embedded_surface_id, InvalidSurfaceId(), 1.f),
+ Quad::RenderPassQuad(pass_ids[0])},
+ {Quad::SolidColorQuad(7), Quad::RenderPassQuad(pass_ids[1])}};
+ Pass root_passes[] = {
+ Pass(root_quads[0], arraysize(root_quads[0]), pass_ids[0]),
+ Pass(root_quads[1], arraysize(root_quads[1]), pass_ids[1]),
+ Pass(root_quads[2], arraysize(root_quads[2]), pass_ids[2])};
+
+ SubmitCompositorFrame(support_.get(), root_passes, arraysize(root_passes),
+ root_local_surface_id_);
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(5u, aggregated_pass_list.size());
+ cc::RenderPassId actual_pass_ids[] = {
+ aggregated_pass_list[0]->id, aggregated_pass_list[1]->id,
+ aggregated_pass_list[2]->id, aggregated_pass_list[3]->id,
+ aggregated_pass_list[4]->id};
+ for (size_t i = 0; i < 5; ++i) {
+ for (size_t j = 0; j < i; ++j) {
+ EXPECT_NE(actual_pass_ids[i], actual_pass_ids[j]);
+ }
+ }
+
+ {
+ SCOPED_TRACE("First pass");
+ // The first pass will just be the first pass from the root surfaces quad
+ // with no render pass quads to remap.
+ TestPassMatchesExpectations(root_passes[0], aggregated_pass_list[0].get());
+ }
+
+ {
+ SCOPED_TRACE("Second pass");
+ // The next two passes will be from the embedded surface since we have to
+ // draw those passes before they are referenced from the render pass draw
+ // quad embedded into the root surface's second pass.
+ // First, there's the first embedded pass which doesn't reference anything
+ // else.
+ TestPassMatchesExpectations(embedded_passes[0],
+ aggregated_pass_list[1].get());
+ }
+
+ {
+ SCOPED_TRACE("Third pass");
+ const auto& third_pass_quad_list = aggregated_pass_list[2]->quad_list;
+ ASSERT_EQ(2u, third_pass_quad_list.size());
+ TestQuadMatchesExpectations(embedded_quads[1][0],
+ third_pass_quad_list.ElementAt(0));
+
+ // This render pass pass quad will reference the first pass from the
+ // embedded surface, which is the second pass in the aggregated frame.
+ ASSERT_EQ(cc::DrawQuad::RENDER_PASS,
+ third_pass_quad_list.ElementAt(1)->material);
+ const auto* third_pass_render_pass_draw_quad =
+ cc::RenderPassDrawQuad::MaterialCast(third_pass_quad_list.ElementAt(1));
+ EXPECT_EQ(actual_pass_ids[1],
+ third_pass_render_pass_draw_quad->render_pass_id);
+ }
+
+ {
+ SCOPED_TRACE("Fourth pass");
+ // The fourth pass will have aggregated quads from the root surface's second
+ // pass and the embedded surface's first pass.
+ const auto& fourth_pass_quad_list = aggregated_pass_list[3]->quad_list;
+ ASSERT_EQ(3u, fourth_pass_quad_list.size());
+
+ // The first quad will be the yellow quad from the embedded surface's last
+ // pass.
+ TestQuadMatchesExpectations(embedded_quads[2][0],
+ fourth_pass_quad_list.ElementAt(0));
+
+ // The next quad will be a render pass quad referencing the second pass from
+ // the embedded surface, which is the third pass in the aggregated frame.
+ ASSERT_EQ(cc::DrawQuad::RENDER_PASS,
+ fourth_pass_quad_list.ElementAt(1)->material);
+ const auto* fourth_pass_first_render_pass_draw_quad =
+ cc::RenderPassDrawQuad::MaterialCast(
+ fourth_pass_quad_list.ElementAt(1));
+ EXPECT_EQ(actual_pass_ids[2],
+ fourth_pass_first_render_pass_draw_quad->render_pass_id);
+
+ // The last quad will be a render pass quad referencing the first pass from
+ // the root surface, which is the first pass overall.
+ ASSERT_EQ(cc::DrawQuad::RENDER_PASS,
+ fourth_pass_quad_list.ElementAt(2)->material);
+ const auto* fourth_pass_second_render_pass_draw_quad =
+ cc::RenderPassDrawQuad::MaterialCast(
+ fourth_pass_quad_list.ElementAt(2));
+ EXPECT_EQ(actual_pass_ids[0],
+ fourth_pass_second_render_pass_draw_quad->render_pass_id);
+ }
+
+ {
+ SCOPED_TRACE("Fifth pass");
+ const auto& fifth_pass_quad_list = aggregated_pass_list[4]->quad_list;
+ ASSERT_EQ(2u, fifth_pass_quad_list.size());
+
+ TestQuadMatchesExpectations(root_quads[2][0],
+ fifth_pass_quad_list.ElementAt(0));
+
+ // The last quad in the last pass will reference the second pass from the
+ // root surface, which after aggregating is the fourth pass in the overall
+ // list.
+ ASSERT_EQ(cc::DrawQuad::RENDER_PASS,
+ fifth_pass_quad_list.ElementAt(1)->material);
+ const auto* fifth_pass_render_pass_draw_quad =
+ cc::RenderPassDrawQuad::MaterialCast(fifth_pass_quad_list.ElementAt(1));
+ EXPECT_EQ(actual_pass_ids[3],
+ fifth_pass_render_pass_draw_quad->render_pass_id);
+ }
+}
+
+// Tests an invalid surface reference in a frame. The surface quad should just
+// be dropped.
+TEST_F(SurfaceAggregatorValidSurfaceTest, InvalidSurfaceReference) {
+ Quad quads[] = {
+ Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SurfaceQuad(InvalidSurfaceId(), InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorBLUE)};
+ Pass passes[] = {Pass(quads, arraysize(quads))};
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ Quad expected_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SolidColorQuad(SK_ColorBLUE)};
+ Pass expected_passes[] = {Pass(expected_quads, arraysize(expected_quads))};
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ SurfaceId ids[] = {root_surface_id, InvalidSurfaceId()};
+
+ AggregateAndVerify(expected_passes, arraysize(expected_passes), ids,
+ arraysize(ids));
+}
+
+// Tests a reference to a valid surface with no submitted frame. This quad
+// should also just be dropped.
+TEST_F(SurfaceAggregatorValidSurfaceTest, ValidSurfaceReferenceWithNoFrame) {
+ LocalSurfaceId empty_local_surface_id = allocator_.GenerateId();
+ SurfaceId surface_with_no_frame_id(support_->frame_sink_id(),
+ empty_local_surface_id);
+
+ Quad quads[] = {
+ Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SurfaceQuad(surface_with_no_frame_id, InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorBLUE)};
+ Pass passes[] = {Pass(quads, arraysize(quads))};
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ Quad expected_quads[] = {Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SolidColorQuad(SK_ColorBLUE)};
+ Pass expected_passes[] = {Pass(expected_quads, arraysize(expected_quads))};
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ SurfaceId ids[] = {root_surface_id, surface_with_no_frame_id};
+ AggregateAndVerify(expected_passes, arraysize(expected_passes), ids,
+ arraysize(ids));
+}
+
+// Tests a surface quad referencing itself, generating a trivial cycle.
+// The quad creating the cycle should be dropped from the final frame.
+TEST_F(SurfaceAggregatorValidSurfaceTest, SimpleCyclicalReference) {
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ Quad quads[] = {Quad::SurfaceQuad(root_surface_id, InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorYELLOW)};
+ Pass passes[] = {Pass(quads, arraysize(quads))};
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ Quad expected_quads[] = {Quad::SolidColorQuad(SK_ColorYELLOW)};
+ Pass expected_passes[] = {Pass(expected_quads, arraysize(expected_quads))};
+ SurfaceId ids[] = {root_surface_id};
+ AggregateAndVerify(expected_passes, arraysize(expected_passes), ids,
+ arraysize(ids));
+}
+
+// Tests a more complex cycle with one intermediate surface.
+TEST_F(SurfaceAggregatorValidSurfaceTest, TwoSurfaceCyclicalReference) {
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+
+ Quad parent_quads[] = {
+ Quad::SolidColorQuad(SK_ColorBLUE),
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorCYAN)};
+ Pass parent_passes[] = {Pass(parent_quads, arraysize(parent_quads))};
+
+ SubmitCompositorFrame(support_.get(), parent_passes, arraysize(parent_passes),
+ root_local_surface_id_);
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ Quad child_quads[] = {
+ Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SurfaceQuad(root_surface_id, InvalidSurfaceId(), 1.f),
+ Quad::SolidColorQuad(SK_ColorMAGENTA)};
+ Pass child_passes[] = {Pass(child_quads, arraysize(child_quads))};
+
+ SubmitCompositorFrame(child_support_.get(), child_passes,
+ arraysize(child_passes), child_local_surface_id);
+
+ // The child surface's reference to the root_surface_ will be dropped, so
+ // we'll end up with:
+ // SK_ColorBLUE from the parent
+ // SK_ColorGREEN from the child
+ // SK_ColorMAGENTA from the child
+ // SK_ColorCYAN from the parent
+ Quad expected_quads[] = {Quad::SolidColorQuad(SK_ColorBLUE),
+ Quad::SolidColorQuad(SK_ColorGREEN),
+ Quad::SolidColorQuad(SK_ColorMAGENTA),
+ Quad::SolidColorQuad(SK_ColorCYAN)};
+ Pass expected_passes[] = {Pass(expected_quads, arraysize(expected_quads))};
+ SurfaceId ids[] = {root_surface_id, child_surface_id};
+ AggregateAndVerify(expected_passes, arraysize(expected_passes), ids,
+ arraysize(ids));
+}
+
+// Tests that we map render pass IDs from different surfaces into a unified
+// namespace and update cc::RenderPassDrawQuad's id references to match.
+TEST_F(SurfaceAggregatorValidSurfaceTest, RenderPassIdMapping) {
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+
+ cc::RenderPassId child_pass_id[] = {1u, 2u};
+ Quad child_quad[][1] = {{Quad::SolidColorQuad(SK_ColorGREEN)},
+ {Quad::RenderPassQuad(child_pass_id[0])}};
+ Pass surface_passes[] = {
+ Pass(child_quad[0], arraysize(child_quad[0]), child_pass_id[0]),
+ Pass(child_quad[1], arraysize(child_quad[1]), child_pass_id[1])};
+
+ SubmitCompositorFrame(child_support_.get(), surface_passes,
+ arraysize(surface_passes), child_local_surface_id);
+
+ // Pass IDs from the parent surface may collide with ones from the child.
+ cc::RenderPassId parent_pass_id[] = {3u, 2u};
+ Quad parent_quad[][1] = {
+ {Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)},
+ {Quad::RenderPassQuad(parent_pass_id[0])}};
+ Pass parent_passes[] = {
+ Pass(parent_quad[0], arraysize(parent_quad[0]), parent_pass_id[0]),
+ Pass(parent_quad[1], arraysize(parent_quad[1]), parent_pass_id[1])};
+
+ SubmitCompositorFrame(support_.get(), parent_passes, arraysize(parent_passes),
+ root_local_surface_id_);
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+ cc::RenderPassId actual_pass_ids[] = {aggregated_pass_list[0]->id,
+ aggregated_pass_list[1]->id,
+ aggregated_pass_list[2]->id};
+ // Make sure the aggregated frame's pass IDs are all unique.
+ for (size_t i = 0; i < 3; ++i) {
+ for (size_t j = 0; j < i; ++j) {
+ EXPECT_NE(actual_pass_ids[j], actual_pass_ids[i])
+ << "pass ids " << i << " and " << j;
+ }
+ }
+
+ // Make sure the render pass quads reference the remapped pass IDs.
+ cc::DrawQuad* render_pass_quads[] = {
+ aggregated_pass_list[1]->quad_list.front(),
+ aggregated_pass_list[2]->quad_list.front()};
+ ASSERT_EQ(render_pass_quads[0]->material, cc::DrawQuad::RENDER_PASS);
+ EXPECT_EQ(actual_pass_ids[0],
+ cc::RenderPassDrawQuad::MaterialCast(render_pass_quads[0])
+ ->render_pass_id);
+
+ ASSERT_EQ(render_pass_quads[1]->material, cc::DrawQuad::RENDER_PASS);
+ EXPECT_EQ(actual_pass_ids[1],
+ cc::RenderPassDrawQuad::MaterialCast(render_pass_quads[1])
+ ->render_pass_id);
+}
+
+void AddSolidColorQuadWithBlendMode(const gfx::Size& size,
+ cc::RenderPass* pass,
+ const SkBlendMode blend_mode) {
+ const gfx::Transform layer_to_target_transform;
+ const gfx::Rect layer_rect(size);
+ const gfx::Rect visible_layer_rect(size);
+ const gfx::Rect clip_rect(size);
+
+ bool is_clipped = false;
+ float opacity = 1.f;
+
+ bool force_anti_aliasing_off = false;
+ auto* sqs = pass->CreateAndAppendSharedQuadState();
+ sqs->SetAll(layer_to_target_transform, layer_rect, visible_layer_rect,
+ clip_rect, is_clipped, opacity, blend_mode, 0);
+
+ auto* color_quad = pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>();
+ color_quad->SetNew(pass->shared_quad_state_list.back(), visible_layer_rect,
+ visible_layer_rect, SK_ColorGREEN,
+ force_anti_aliasing_off);
+}
+
+// This tests that we update shared quad state pointers correctly within
+// aggregated passes. The shared quad state list on the aggregated pass will
+// include the shared quad states from each pass in one list so the quads will
+// end up pointed to shared quad state objects at different offsets. This test
+// uses the blend_mode value stored on the shared quad state to track the shared
+// quad state, but anything saved on the shared quad state would work.
+//
+// This test has 4 surfaces in the following structure:
+// root_surface -> quad with kClear_Mode,
+// [child_one_surface],
+// quad with kDstOver_Mode,
+// [child_two_surface],
+// quad with kDstIn_Mode
+// child_one_surface -> quad with kSrc_Mode,
+// [grandchild_surface],
+// quad with kSrcOver_Mode
+// child_two_surface -> quad with kSrcIn_Mode
+// grandchild_surface -> quad with kDst_Mode
+//
+// Resulting in the following aggregated pass:
+// quad_root_0 - blend_mode kClear_Mode
+// quad_child_one_0 - blend_mode kSrc_Mode
+// quad_grandchild_0 - blend_mode kDst_Mode
+// quad_child_one_1 - blend_mode kSrcOver_Mode
+// quad_root_1 - blend_mode kDstOver_Mode
+// quad_child_two_0 - blend_mode kSrcIn_Mode
+// quad_root_2 - blend_mode kDstIn_Mode
+TEST_F(SurfaceAggregatorValidSurfaceTest, AggregateSharedQuadStateProperties) {
+ const SkBlendMode blend_modes[] = {
+ SkBlendMode::kClear, // 0
+ SkBlendMode::kSrc, // 1
+ SkBlendMode::kDst, // 2
+ SkBlendMode::kSrcOver, // 3
+ SkBlendMode::kDstOver, // 4
+ SkBlendMode::kSrcIn, // 5
+ SkBlendMode::kDstIn, // 6
+ };
+ auto grandchild_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId1, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ auto child_one_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId2, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ auto child_two_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId3, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ int pass_id = 1;
+ LocalSurfaceId grandchild_local_surface_id = allocator_.GenerateId();
+ SurfaceId grandchild_surface_id(grandchild_support->frame_sink_id(),
+ grandchild_local_surface_id);
+
+ auto grandchild_pass = cc::RenderPass::Create();
+ gfx::Rect output_rect(SurfaceSize());
+ gfx::Rect damage_rect(SurfaceSize());
+ gfx::Transform transform_to_root_target;
+ grandchild_pass->SetNew(pass_id, output_rect, damage_rect,
+ transform_to_root_target);
+ AddSolidColorQuadWithBlendMode(SurfaceSize(), grandchild_pass.get(),
+ blend_modes[2]);
+ QueuePassAsFrame(std::move(grandchild_pass), grandchild_local_surface_id,
+ grandchild_support.get());
+
+ LocalSurfaceId child_one_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_one_surface_id(child_one_support->frame_sink_id(),
+ child_one_local_surface_id);
+
+ auto child_one_pass = cc::RenderPass::Create();
+ child_one_pass->SetNew(pass_id, output_rect, damage_rect,
+ transform_to_root_target);
+ AddSolidColorQuadWithBlendMode(SurfaceSize(), child_one_pass.get(),
+ blend_modes[1]);
+ auto* grandchild_surface_quad =
+ child_one_pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ grandchild_surface_quad->SetNew(
+ child_one_pass->shared_quad_state_list.back(), gfx::Rect(SurfaceSize()),
+ gfx::Rect(SurfaceSize()), grandchild_surface_id,
+ cc::SurfaceDrawQuadType::PRIMARY, nullptr);
+ AddSolidColorQuadWithBlendMode(SurfaceSize(), child_one_pass.get(),
+ blend_modes[3]);
+ QueuePassAsFrame(std::move(child_one_pass), child_one_local_surface_id,
+ child_one_support.get());
+
+ LocalSurfaceId child_two_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_two_surface_id(child_two_support->frame_sink_id(),
+ child_two_local_surface_id);
+
+ auto child_two_pass = cc::RenderPass::Create();
+ child_two_pass->SetNew(pass_id, output_rect, damage_rect,
+ transform_to_root_target);
+ AddSolidColorQuadWithBlendMode(SurfaceSize(), child_two_pass.get(),
+ blend_modes[5]);
+ QueuePassAsFrame(std::move(child_two_pass), child_two_local_surface_id,
+ child_two_support.get());
+
+ auto root_pass = cc::RenderPass::Create();
+ root_pass->SetNew(pass_id, output_rect, damage_rect,
+ transform_to_root_target);
+
+ AddSolidColorQuadWithBlendMode(SurfaceSize(), root_pass.get(),
+ blend_modes[0]);
+ auto* child_one_surface_quad =
+ root_pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ child_one_surface_quad->SetNew(root_pass->shared_quad_state_list.back(),
+ gfx::Rect(SurfaceSize()),
+ gfx::Rect(SurfaceSize()), child_one_surface_id,
+ cc::SurfaceDrawQuadType::PRIMARY, nullptr);
+ AddSolidColorQuadWithBlendMode(SurfaceSize(), root_pass.get(),
+ blend_modes[4]);
+ auto* child_two_surface_quad =
+ root_pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ child_two_surface_quad->SetNew(root_pass->shared_quad_state_list.back(),
+ gfx::Rect(SurfaceSize()),
+ gfx::Rect(SurfaceSize()), child_two_surface_id,
+ cc::SurfaceDrawQuadType::PRIMARY, nullptr);
+ AddSolidColorQuadWithBlendMode(SurfaceSize(), root_pass.get(),
+ blend_modes[6]);
+
+ QueuePassAsFrame(std::move(root_pass), root_local_surface_id_,
+ support_.get());
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(1u, aggregated_pass_list.size());
+
+ const auto& aggregated_quad_list = aggregated_pass_list[0]->quad_list;
+
+ ASSERT_EQ(7u, aggregated_quad_list.size());
+
+ for (auto iter = aggregated_quad_list.cbegin();
+ iter != aggregated_quad_list.cend(); ++iter) {
+ EXPECT_EQ(blend_modes[iter.index()], iter->shared_quad_state->blend_mode)
+ << iter.index();
+ }
+
+ grandchild_support->EvictCurrentSurface();
+ child_one_support->EvictCurrentSurface();
+ child_two_support->EvictCurrentSurface();
+}
+
+// This tests that when aggregating a frame with multiple render passes that we
+// map the transforms for the root pass but do not modify the transform on child
+// passes.
+//
+// The root surface has one pass with a surface quad transformed by +10 in the y
+// direction.
+//
+// The middle surface has one pass with a surface quad scaled by 2 in the x
+// and 3 in the y directions.
+//
+// The child surface has two passes. The first pass has a quad with a transform
+// of +5 in the x direction. The second pass has a reference to the first pass'
+// pass id and a transform of +8 in the x direction.
+//
+// After aggregation, the child surface's root pass quad should have all
+// transforms concatenated for a total transform of +23 x, +10 y. The
+// contributing render pass' transform in the aggregate frame should not be
+// affected.
+TEST_F(SurfaceAggregatorValidSurfaceTest, AggregateMultiplePassWithTransform) {
+ auto middle_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryMiddleFrameSinkId, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ // Innermost child surface.
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+ {
+ int child_pass_id[] = {1, 2};
+ Quad child_quads[][1] = {
+ {Quad::SolidColorQuad(SK_ColorGREEN)},
+ {Quad::RenderPassQuad(child_pass_id[0])},
+ };
+ Pass child_passes[] = {
+ Pass(child_quads[0], arraysize(child_quads[0]), child_pass_id[0]),
+ Pass(child_quads[1], arraysize(child_quads[1]), child_pass_id[1])};
+
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_passes, arraysize(child_passes));
+
+ auto* child_nonroot_pass = child_frame.render_pass_list[0].get();
+ child_nonroot_pass->transform_to_root_target.Translate(8, 0);
+ auto* child_nonroot_pass_sqs =
+ child_nonroot_pass->shared_quad_state_list.front();
+ child_nonroot_pass_sqs->quad_to_target_transform.Translate(5, 0);
+
+ auto* child_root_pass = child_frame.render_pass_list[1].get();
+ auto* child_root_pass_sqs = child_root_pass->shared_quad_state_list.front();
+ child_root_pass_sqs->quad_to_target_transform.Translate(8, 0);
+ child_root_pass_sqs->is_clipped = true;
+ child_root_pass_sqs->clip_rect = gfx::Rect(0, 0, 5, 5);
+
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+ }
+
+ // Middle child surface.
+ LocalSurfaceId middle_local_surface_id = allocator_.GenerateId();
+ SurfaceId middle_surface_id(middle_support->frame_sink_id(),
+ middle_local_surface_id);
+ {
+ Quad middle_quads[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+ Pass middle_passes[] = {
+ Pass(middle_quads, arraysize(middle_quads)),
+ };
+
+ cc::CompositorFrame middle_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&middle_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ middle_passes, arraysize(middle_passes));
+
+ auto* middle_root_pass = middle_frame.render_pass_list[0].get();
+ middle_root_pass->quad_list.ElementAt(0)->visible_rect =
+ gfx::Rect(0, 1, 100, 7);
+ auto* middle_root_pass_sqs =
+ middle_root_pass->shared_quad_state_list.front();
+ middle_root_pass_sqs->quad_to_target_transform.Scale(2, 3);
+
+ middle_support->SubmitCompositorFrame(middle_local_surface_id,
+ std::move(middle_frame));
+ }
+
+ // Root surface.
+ Quad secondary_quads[] = {
+ Quad::SolidColorQuad(1),
+ Quad::SurfaceQuad(middle_surface_id, InvalidSurfaceId(), 1.f)};
+ Quad root_quads[] = {Quad::SolidColorQuad(1)};
+ Pass root_passes[] = {Pass(secondary_quads, arraysize(secondary_quads)),
+ Pass(root_quads, arraysize(root_quads))};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ root_frame.render_pass_list[0]
+ ->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(0, 7);
+ root_frame.render_pass_list[0]
+ ->shared_quad_state_list.ElementAt(1)
+ ->quad_to_target_transform.Translate(0, 10);
+ root_frame.render_pass_list[0]->quad_list.ElementAt(1)->visible_rect =
+ gfx::Rect(0, 0, 8, 100);
+
+ root_frame.render_pass_list[0]->transform_to_root_target.Translate(10, 5);
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+
+ ASSERT_EQ(1u, aggregated_pass_list[0]->shared_quad_state_list.size());
+
+ // The first pass should have one shared quad state for the one solid color
+ // quad.
+ EXPECT_EQ(1u, aggregated_pass_list[0]->shared_quad_state_list.size());
+ // The second pass should have just two shared quad states. We'll
+ // verify the properties through the quads.
+ EXPECT_EQ(2u, aggregated_pass_list[1]->shared_quad_state_list.size());
+
+ EXPECT_EQ(1u, aggregated_pass_list[2]->shared_quad_state_list.size());
+
+ auto* aggregated_first_pass_sqs =
+ aggregated_pass_list[0]->shared_quad_state_list.front();
+
+ // The first pass's transform should be unaffected by the embedding and still
+ // be a translation by +5 in the x direction.
+ gfx::Transform expected_aggregated_first_pass_sqs_transform;
+ expected_aggregated_first_pass_sqs_transform.Translate(5, 0);
+ EXPECT_EQ(expected_aggregated_first_pass_sqs_transform.ToString(),
+ aggregated_first_pass_sqs->quad_to_target_transform.ToString());
+
+ // The first pass's transform to the root target should include the aggregated
+ // transform, including the transform from the child pass to the root.
+ gfx::Transform expected_first_pass_transform_to_root_target;
+ expected_first_pass_transform_to_root_target.Translate(10, 5);
+ expected_first_pass_transform_to_root_target.Translate(0, 10);
+ expected_first_pass_transform_to_root_target.Scale(2, 3);
+ expected_first_pass_transform_to_root_target.Translate(8, 0);
+ EXPECT_EQ(expected_first_pass_transform_to_root_target.ToString(),
+ aggregated_pass_list[0]->transform_to_root_target.ToString());
+
+ ASSERT_EQ(2u, aggregated_pass_list[1]->quad_list.size());
+
+ gfx::Transform expected_root_pass_quad_transforms[2];
+ // The first quad in the root pass is the solid color quad from the original
+ // root surface. Its transform should be unaffected by the aggregation and
+ // still be +7 in the y direction.
+ expected_root_pass_quad_transforms[0].Translate(0, 7);
+ // The second quad in the root pass is aggregated from the child surface so
+ // its transform should be the combination of its original translation
+ // (0, 10), the middle surface draw quad's scale of (2, 3), and the
+ // child surface draw quad's translation (8, 0).
+ expected_root_pass_quad_transforms[1].Translate(0, 10);
+ expected_root_pass_quad_transforms[1].Scale(2, 3);
+ expected_root_pass_quad_transforms[1].Translate(8, 0);
+
+ for (auto iter = aggregated_pass_list[1]->quad_list.cbegin();
+ iter != aggregated_pass_list[1]->quad_list.cend(); ++iter) {
+ EXPECT_EQ(expected_root_pass_quad_transforms[iter.index()].ToString(),
+ iter->shared_quad_state->quad_to_target_transform.ToString())
+ << iter.index();
+ }
+
+ EXPECT_TRUE(
+ aggregated_pass_list[1]->shared_quad_state_list.ElementAt(1)->is_clipped);
+
+ // The second quad in the root pass is aggregated from the child, so its
+ // clip rect must be transformed by the child's translation/scale and
+ // clipped be the visible_rects for both children.
+ EXPECT_EQ(gfx::Rect(0, 13, 8, 12).ToString(),
+ aggregated_pass_list[1]
+ ->shared_quad_state_list.ElementAt(1)
+ ->clip_rect.ToString());
+
+ middle_support->EvictCurrentSurface();
+}
+
+// Tests that damage rects are aggregated correctly when surfaces change.
+TEST_F(SurfaceAggregatorValidSurfaceTest, AggregateDamageRect) {
+ auto parent_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryMiddleFrameSinkId, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ Quad child_quads[] = {Quad::RenderPassQuad(1)};
+ Pass child_passes[] = {Pass(child_quads, arraysize(child_quads), 1)};
+
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_passes, arraysize(child_passes));
+
+ auto* child_root_pass = child_frame.render_pass_list[0].get();
+ auto* child_root_pass_sqs = child_root_pass->shared_quad_state_list.front();
+ child_root_pass_sqs->quad_to_target_transform.Translate(8, 0);
+
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+
+ Quad parent_surface_quads[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+ Pass parent_surface_passes[] = {
+ Pass(parent_surface_quads, arraysize(parent_surface_quads), 1)};
+
+ // Parent surface is only used to test if the transform is applied correctly
+ // to the child surface's damage.
+ cc::CompositorFrame parent_surface_frame =
+ cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&parent_surface_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ parent_surface_passes, arraysize(parent_surface_passes));
+
+ LocalSurfaceId parent_local_surface_id = allocator_.GenerateId();
+ SurfaceId parent_surface_id(parent_support->frame_sink_id(),
+ parent_local_surface_id);
+ parent_support->SubmitCompositorFrame(parent_local_surface_id,
+ std::move(parent_surface_frame));
+
+ Quad root_surface_quads[] = {
+ Quad::SurfaceQuad(parent_surface_id, InvalidSurfaceId(), 1.f)};
+ Quad root_render_pass_quads[] = {Quad::RenderPassQuad(1)};
+
+ Pass root_passes[] = {
+ Pass(root_surface_quads, arraysize(root_surface_quads), 1),
+ Pass(root_render_pass_quads, arraysize(root_render_pass_quads), 2)};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ root_frame.render_pass_list[0]
+ ->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(0, 10);
+ root_frame.render_pass_list[0]->damage_rect = gfx::Rect(5, 5, 10, 10);
+ root_frame.render_pass_list[1]->damage_rect = gfx::Rect(5, 5, 100, 100);
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(2u, aggregated_pass_list.size());
+
+ // Damage rect for first aggregation should contain entire root surface.
+ EXPECT_TRUE(
+ aggregated_pass_list[1]->damage_rect.Contains(gfx::Rect(SurfaceSize())));
+
+ {
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_passes, arraysize(child_passes));
+
+ auto* child_root_pass = child_frame.render_pass_list[0].get();
+ auto* child_root_pass_sqs = child_root_pass->shared_quad_state_list.front();
+ child_root_pass_sqs->quad_to_target_transform.Translate(8, 0);
+ child_root_pass->damage_rect = gfx::Rect(10, 10, 10, 10);
+
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(),
+ root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(2u, aggregated_pass_list.size());
+
+ // Outer surface didn't change, so transformed inner damage rect should be
+ // used.
+ EXPECT_EQ(gfx::Rect(10, 20, 10, 10).ToString(),
+ aggregated_pass_list[1]->damage_rect.ToString());
+ }
+
+ {
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ root_passes, arraysize(root_passes));
+
+ root_frame.render_pass_list[0]
+ ->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(0, 10);
+ root_frame.render_pass_list[0]->damage_rect = gfx::Rect(0, 0, 1, 1);
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+ }
+
+ {
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ root_passes, arraysize(root_passes));
+
+ root_frame.render_pass_list[0]
+ ->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(0, 10);
+ root_frame.render_pass_list[0]->damage_rect = gfx::Rect(1, 1, 1, 1);
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(),
+ root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(2u, aggregated_pass_list.size());
+
+ // The root surface was enqueued without being aggregated once, so it should
+ // be treated as completely damaged.
+ EXPECT_TRUE(aggregated_pass_list[1]->damage_rect.Contains(
+ gfx::Rect(SurfaceSize())));
+ }
+
+ // No Surface changed, so no damage should be given.
+ {
+ SurfaceId root_surface_id(support_->frame_sink_id(),
+ root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(2u, aggregated_pass_list.size());
+
+ EXPECT_TRUE(aggregated_pass_list[1]->damage_rect.IsEmpty());
+ }
+
+ // SetFullDamageRectForSurface should cause the entire output to be
+ // marked as damaged.
+ {
+ aggregator_.SetFullDamageForSurface(root_surface_id);
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(2u, aggregated_pass_list.size());
+
+ EXPECT_TRUE(aggregated_pass_list[1]->damage_rect.Contains(
+ gfx::Rect(SurfaceSize())));
+ }
+
+ parent_support->EvictCurrentSurface();
+}
+
+// Check that damage is correctly calculated for surfaces.
+TEST_F(SurfaceAggregatorValidSurfaceTest, SwitchSurfaceDamage) {
+ Quad root_render_pass_quads[] = {Quad::SolidColorQuad(1)};
+
+ Pass root_passes[] = {
+ Pass(root_render_pass_quads, arraysize(root_render_pass_quads), 2)};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ root_frame.render_pass_list[0]->damage_rect = gfx::Rect(5, 5, 100, 100);
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ {
+ SurfaceId root_surface_id(support_->frame_sink_id(),
+ root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(1u, aggregated_pass_list.size());
+
+ // Damage rect for first aggregation should contain entire root surface.
+ EXPECT_TRUE(aggregated_pass_list[0]->damage_rect.Contains(
+ gfx::Rect(SurfaceSize())));
+ }
+
+ LocalSurfaceId second_root_local_surface_id = allocator_.GenerateId();
+ SurfaceId second_root_surface_id(support_->frame_sink_id(),
+ second_root_local_surface_id);
+ {
+ Quad root_render_pass_quads[] = {Quad::SolidColorQuad(1)};
+
+ Pass root_passes[] = {
+ Pass(root_render_pass_quads, arraysize(root_render_pass_quads), 2)};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ root_passes, arraysize(root_passes));
+
+ root_frame.render_pass_list[0]->damage_rect = gfx::Rect(1, 2, 3, 4);
+
+ support_->SubmitCompositorFrame(second_root_local_surface_id,
+ std::move(root_frame));
+ }
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(second_root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(1u, aggregated_pass_list.size());
+
+ EXPECT_EQ(gfx::Rect(1, 2, 3, 4), aggregated_pass_list[0]->damage_rect);
+ }
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(second_root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(1u, aggregated_pass_list.size());
+
+ // No new frame, so no new damage.
+ EXPECT_TRUE(aggregated_pass_list[0]->damage_rect.IsEmpty());
+ }
+}
+
+class SurfaceAggregatorPartialSwapTest
+ : public SurfaceAggregatorValidSurfaceTest {
+ public:
+ SurfaceAggregatorPartialSwapTest()
+ : SurfaceAggregatorValidSurfaceTest(true) {}
+};
+
+// Tests that quads outside the damage rect are ignored.
+TEST_F(SurfaceAggregatorPartialSwapTest, IgnoreOutside) {
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+ // The child surface has three quads, one with a visible rect of 13,13 4x4 and
+ // the other other with a visible rect of 10,10 2x2 (relative to root target
+ // space), and one with a non-invertible transform.
+ {
+ int child_pass_id = 1;
+ Quad child_quads1[] = {Quad::RenderPassQuad(child_pass_id)};
+ Quad child_quads2[] = {Quad::RenderPassQuad(child_pass_id)};
+ Quad child_quads3[] = {Quad::RenderPassQuad(child_pass_id)};
+ Pass child_passes[] = {
+ Pass(child_quads1, arraysize(child_quads1), child_pass_id),
+ Pass(child_quads2, arraysize(child_quads2), child_pass_id),
+ Pass(child_quads3, arraysize(child_quads2), child_pass_id)};
+
+ cc::RenderPassList child_pass_list;
+ AddPasses(&child_pass_list, gfx::Rect(SurfaceSize()), child_passes,
+ arraysize(child_passes));
+
+ child_pass_list[0]->quad_list.ElementAt(0)->visible_rect =
+ gfx::Rect(1, 1, 2, 2);
+ auto* child_sqs = child_pass_list[0]->shared_quad_state_list.ElementAt(0u);
+ child_sqs->quad_to_target_transform.Translate(1, 1);
+ child_sqs->quad_to_target_transform.Scale(2, 2);
+
+ child_pass_list[1]->quad_list.ElementAt(0)->visible_rect =
+ gfx::Rect(0, 0, 2, 2);
+
+ auto* child_noninvertible_sqs =
+ child_pass_list[2]->shared_quad_state_list.ElementAt(0u);
+ child_noninvertible_sqs->quad_to_target_transform.matrix().setDouble(0, 0,
+ 0.0);
+ EXPECT_FALSE(
+ child_noninvertible_sqs->quad_to_target_transform.IsInvertible());
+ child_pass_list[2]->quad_list.ElementAt(0)->visible_rect =
+ gfx::Rect(0, 0, 2, 2);
+
+ SubmitPassListAsFrame(child_support_.get(), child_local_surface_id,
+ &child_pass_list);
+ }
+
+ {
+ Quad root_quads[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+
+ Pass root_passes[] = {Pass(root_quads, arraysize(root_quads))};
+
+ cc::RenderPassList root_pass_list;
+ AddPasses(&root_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ auto* root_pass = root_pass_list[0].get();
+ root_pass->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(10, 10);
+ root_pass->damage_rect = gfx::Rect(0, 0, 1, 1);
+
+ SubmitPassListAsFrame(support_.get(), root_local_surface_id_,
+ &root_pass_list);
+ }
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+
+ // Damage rect for first aggregation should contain entire root surface.
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[2]->damage_rect);
+ EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+
+ // Create a root surface with a smaller damage rect.
+ {
+ Quad root_quads[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+
+ Pass root_passes[] = {Pass(root_quads, arraysize(root_quads))};
+
+ cc::RenderPassList root_pass_list;
+ AddPasses(&root_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ auto* root_pass = root_pass_list[0].get();
+ root_pass->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(10, 10);
+ root_pass->damage_rect = gfx::Rect(10, 10, 2, 2);
+ SubmitPassListAsFrame(support_.get(), root_local_surface_id_,
+ &root_pass_list);
+ }
+
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+
+ // Only first quad from surface is inside damage rect and should be
+ // included.
+ EXPECT_EQ(gfx::Rect(10, 10, 2, 2), aggregated_pass_list[2]->damage_rect);
+ EXPECT_EQ(0u, aggregated_pass_list[0]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(0, 0, 2, 2),
+ aggregated_pass_list[1]->quad_list.back()->visible_rect);
+ EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+ }
+
+ // New child frame has same content and no damage, but has a
+ // CopyOutputRequest.
+ {
+ int child_pass_ids[] = {1, 2};
+ Quad child_quads1[] = {Quad::SolidColorQuad(1)};
+ Quad child_quads2[] = {Quad::RenderPassQuad(child_pass_ids[0])};
+ Pass child_passes[] = {
+ Pass(child_quads1, arraysize(child_quads1), child_pass_ids[0]),
+ Pass(child_quads2, arraysize(child_quads2), child_pass_ids[1])};
+
+ cc::RenderPassList child_pass_list;
+ AddPasses(&child_pass_list, gfx::Rect(SurfaceSize()), child_passes,
+ arraysize(child_passes));
+
+ child_pass_list[0]->quad_list.ElementAt(0)->visible_rect =
+ gfx::Rect(1, 1, 2, 2);
+ auto* child_sqs = child_pass_list[0]->shared_quad_state_list.ElementAt(0u);
+ child_sqs->quad_to_target_transform.Translate(1, 1);
+ child_sqs->quad_to_target_transform.Scale(2, 2);
+
+ child_pass_list[1]->quad_list.ElementAt(0)->visible_rect =
+ gfx::Rect(0, 0, 2, 2);
+
+ auto* child_root_pass = child_pass_list[1].get();
+
+ child_root_pass->copy_requests.push_back(
+ cc::CopyOutputRequest::CreateEmptyRequest());
+ child_root_pass->damage_rect = gfx::Rect();
+ SubmitPassListAsFrame(child_support_.get(), child_local_surface_id,
+ &child_pass_list);
+ }
+
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ // Output frame should have no damage, but all quads included.
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[1]->damage_rect);
+ EXPECT_TRUE(aggregated_pass_list[2]->damage_rect.IsEmpty());
+ ASSERT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+ ASSERT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(1, 1, 2, 2),
+ aggregated_pass_list[0]->quad_list.ElementAt(0)->visible_rect);
+ EXPECT_EQ(gfx::Rect(0, 0, 2, 2),
+ aggregated_pass_list[1]->quad_list.ElementAt(0)->visible_rect);
+ ASSERT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+ }
+
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+ // There were no changes since last aggregation, so output should be empty
+ // and have no damage.
+ ASSERT_EQ(1u, aggregated_pass_list.size());
+ EXPECT_TRUE(aggregated_pass_list[0]->damage_rect.IsEmpty());
+ ASSERT_EQ(0u, aggregated_pass_list[0]->quad_list.size());
+ }
+
+ // Root surface has smaller damage rect, but filter on render pass means all
+ // of it and its descendant passes should be aggregated.
+ {
+ int root_pass_ids[] = {1, 2, 3};
+ Quad root_quads1[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+ Quad root_quads2[] = {Quad::RenderPassQuad(root_pass_ids[0])};
+ Quad root_quads3[] = {Quad::RenderPassQuad(root_pass_ids[1])};
+ Pass root_passes[] = {
+ Pass(root_quads1, arraysize(root_quads1), root_pass_ids[0]),
+ Pass(root_quads2, arraysize(root_quads2), root_pass_ids[1]),
+ Pass(root_quads3, arraysize(root_quads3), root_pass_ids[2])};
+
+ cc::RenderPassList root_pass_list;
+ AddPasses(&root_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ auto* filter_pass = root_pass_list[1].get();
+ filter_pass->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(10, 10);
+ auto* root_pass = root_pass_list[2].get();
+ filter_pass->filters.Append(cc::FilterOperation::CreateBlurFilter(2));
+ root_pass->damage_rect = gfx::Rect(10, 10, 2, 2);
+ SubmitPassListAsFrame(support_.get(), root_local_surface_id_,
+ &root_pass_list);
+ }
+
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(4u, aggregated_pass_list.size());
+
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[1]->damage_rect);
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[2]->damage_rect);
+ EXPECT_EQ(gfx::Rect(10, 10, 2, 2), aggregated_pass_list[3]->damage_rect);
+ EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+ // First render pass draw quad is outside damage rect, so shouldn't be
+ // drawn.
+ EXPECT_EQ(0u, aggregated_pass_list[3]->quad_list.size());
+ }
+
+ // Root surface has smaller damage rect. Opacity filter on render pass
+ // means Surface quad under it should be aggregated.
+ {
+ int root_pass_ids[] = {1, 2};
+ Quad root_quads1[] = {
+ Quad::SolidColorQuad(1),
+ };
+ Quad root_quads2[] = {
+ Quad::RenderPassQuad(root_pass_ids[0]),
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+ Pass root_passes[] = {
+ Pass(root_quads1, arraysize(root_quads1), root_pass_ids[0]),
+ Pass(root_quads2, arraysize(root_quads2), root_pass_ids[1])};
+
+ cc::RenderPassList root_pass_list;
+ AddPasses(&root_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ auto* pass = root_pass_list[0].get();
+ auto* root_pass = root_pass_list[1].get();
+ root_pass->shared_quad_state_list.ElementAt(1)
+ ->quad_to_target_transform.Translate(10, 10);
+ pass->background_filters.Append(
+ cc::FilterOperation::CreateOpacityFilter(0.5f));
+ root_pass->damage_rect = gfx::Rect(10, 10, 2, 2);
+ SubmitPassListAsFrame(support_.get(), root_local_surface_id_,
+ &root_pass_list);
+ }
+
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+
+ // Pass 0 is solid color quad from root, but outside damage rect.
+ EXPECT_EQ(gfx::Rect(10, 10, 2, 2), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(0u, aggregated_pass_list[0]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(0, 0, 2, 2), aggregated_pass_list[1]->damage_rect);
+ EXPECT_EQ(0u, aggregated_pass_list[1]->quad_list.size());
+
+ // First render pass draw quad is outside damage rect, so shouldn't be
+ // drawn. SurfaceDrawQuad is after opacity filter, so corresponding
+ // cc::RenderPassDrawQuad should be drawn.
+ EXPECT_EQ(gfx::Rect(10, 10, 2, 2), aggregated_pass_list[2]->damage_rect);
+ EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+ }
+
+ // TODO(wutao): Partial swap does not work with pixel moving background
+ // filter. See https://crbug.com/737255.
+ // Has background filter on render pass will make the whole output rect as
+ // damaged.
+ {
+ int root_pass_ids[] = {1, 2};
+ Quad root_quads1[] = {
+ Quad::SolidColorQuad(1),
+ };
+ Quad root_quads2[] = {
+ Quad::RenderPassQuad(root_pass_ids[0]),
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+ Pass root_passes[] = {
+ Pass(root_quads1, arraysize(root_quads1), root_pass_ids[0]),
+ Pass(root_quads2, arraysize(root_quads2), root_pass_ids[1])};
+
+ cc::RenderPassList root_pass_list;
+ AddPasses(&root_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ auto* pass = root_pass_list[0].get();
+ auto* root_pass = root_pass_list[1].get();
+ root_pass->shared_quad_state_list.ElementAt(1)
+ ->quad_to_target_transform.Translate(10, 10);
+ pass->background_filters.Append(cc::FilterOperation::CreateBlurFilter(2));
+ root_pass->damage_rect = gfx::Rect(10, 10, 2, 2);
+ SubmitPassListAsFrame(support_.get(), root_local_surface_id_,
+ &root_pass_list);
+ }
+
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+
+ // Pass 0 has background blur filter, so should be drawn.
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[1]->damage_rect);
+ EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+
+ // First render pass draw quad is outside damage rect but has background
+ // filter, so should be drawn. SurfaceDrawQuad is after background filter,
+ // so corresponding cc::RenderPassDrawQuad should be drawn.
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[2]->damage_rect);
+ EXPECT_EQ(2u, aggregated_pass_list[2]->quad_list.size());
+ }
+}
+
+class SurfaceAggregatorWithResourcesTest : public testing::Test {
+ public:
+ void SetUp() override {
+ shared_bitmap_manager_ = base::MakeUnique<cc::TestSharedBitmapManager>();
+ resource_provider_ =
+ cc::FakeResourceProvider::Create(nullptr, shared_bitmap_manager_.get());
+
+ aggregator_ = base::MakeUnique<SurfaceAggregator>(
+ manager_.surface_manager(), resource_provider_.get(), false);
+ aggregator_->set_output_is_secure(true);
+ }
+
+ protected:
+ FrameSinkManagerImpl manager_;
+ std::unique_ptr<SharedBitmapManager> shared_bitmap_manager_;
+ std::unique_ptr<cc::ResourceProvider> resource_provider_;
+ std::unique_ptr<SurfaceAggregator> aggregator_;
+};
+
+void SubmitCompositorFrameWithResources(cc::ResourceId* resource_ids,
+ size_t num_resource_ids,
+ bool valid,
+ SurfaceId child_id,
+ CompositorFrameSinkSupport* support,
+ SurfaceId surface_id) {
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(1, gfx::Rect(0, 0, 20, 20), gfx::Rect(), gfx::Transform());
+ auto* sqs = pass->CreateAndAppendSharedQuadState();
+ sqs->opacity = 1.f;
+ if (child_id.is_valid()) {
+ auto* surface_quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+ surface_quad->SetNew(sqs, gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1),
+ child_id, cc::SurfaceDrawQuadType::PRIMARY, nullptr);
+ }
+
+ for (size_t i = 0u; i < num_resource_ids; ++i) {
+ cc::TransferableResource resource;
+ resource.id = resource_ids[i];
+ // ResourceProvider is software, so only software resources are valid.
+ resource.is_software = valid;
+ frame.resource_list.push_back(resource);
+ auto* quad = pass->CreateAndAppendDrawQuad<cc::TextureDrawQuad>();
+ const gfx::Rect rect;
+ const gfx::Rect opaque_rect;
+ const gfx::Rect visible_rect;
+ bool needs_blending = false;
+ bool premultiplied_alpha = false;
+ const gfx::PointF uv_top_left;
+ const gfx::PointF uv_bottom_right;
+ SkColor background_color = SK_ColorGREEN;
+ const float vertex_opacity[4] = {0.f, 0.f, 1.f, 1.f};
+ bool flipped = false;
+ bool nearest_neighbor = false;
+ bool secure_output_only = true;
+ quad->SetAll(sqs, rect, opaque_rect, visible_rect, needs_blending,
+ resource_ids[i], gfx::Size(), premultiplied_alpha, uv_top_left,
+ uv_bottom_right, background_color, vertex_opacity, flipped,
+ nearest_neighbor, secure_output_only);
+ }
+ frame.render_pass_list.push_back(std::move(pass));
+ support->SubmitCompositorFrame(surface_id.local_surface_id(),
+ std::move(frame));
+}
+
+TEST_F(SurfaceAggregatorWithResourcesTest, TakeResourcesOneSurface) {
+ cc::FakeCompositorFrameSinkSupportClient client;
+ auto support = CompositorFrameSinkSupport::Create(
+ &client, &manager_, kArbitraryRootFrameSinkId, kRootIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_surface_id(7u, base::UnguessableToken::Create());
+ SurfaceId surface_id(support->frame_sink_id(), local_surface_id);
+
+ cc::ResourceId ids[] = {11, 12, 13};
+ SubmitCompositorFrameWithResources(ids, arraysize(ids), true, SurfaceId(),
+ support.get(), surface_id);
+
+ cc::CompositorFrame frame = aggregator_->Aggregate(surface_id);
+
+ // Nothing should be available to be returned yet.
+ EXPECT_TRUE(client.returned_resources().empty());
+
+ SubmitCompositorFrameWithResources(NULL, 0u, true, SurfaceId(), support.get(),
+ surface_id);
+
+ frame = aggregator_->Aggregate(surface_id);
+
+ ASSERT_EQ(3u, client.returned_resources().size());
+ cc::ResourceId returned_ids[3];
+ for (size_t i = 0; i < 3; ++i) {
+ returned_ids[i] = client.returned_resources()[i].id;
+ }
+ EXPECT_THAT(returned_ids,
+ testing::WhenSorted(testing::ElementsAreArray(ids)));
+
+ support->EvictCurrentSurface();
+}
+
+// This test verifies that when a CompositorFrame is submitted to a new surface
+// ID, and a new display frame is generated, then the resources of the old
+// surface are returned to the appropriate client.
+TEST_F(SurfaceAggregatorWithResourcesTest, ReturnResourcesAsSurfacesChange) {
+ cc::FakeCompositorFrameSinkSupportClient client;
+ auto support = CompositorFrameSinkSupport::Create(
+ &client, &manager_, kArbitraryRootFrameSinkId, kRootIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_surface_id1(7u, base::UnguessableToken::Create());
+ LocalSurfaceId local_surface_id2(8u, base::UnguessableToken::Create());
+ SurfaceId surface_id1(support->frame_sink_id(), local_surface_id1);
+ SurfaceId surface_id2(support->frame_sink_id(), local_surface_id2);
+
+ cc::ResourceId ids[] = {11, 12, 13};
+ SubmitCompositorFrameWithResources(ids, arraysize(ids), true, SurfaceId(),
+ support.get(), surface_id1);
+
+ cc::CompositorFrame frame = aggregator_->Aggregate(surface_id1);
+
+ // Nothing should be available to be returned yet.
+ EXPECT_TRUE(client.returned_resources().empty());
+
+ // Submitting a CompositorFrame to |surface_id2| should cause the surface
+ // associated with |surface_id1| to get garbage collected.
+ SubmitCompositorFrameWithResources(NULL, 0u, true, SurfaceId(), support.get(),
+ surface_id2);
+
+ frame = aggregator_->Aggregate(surface_id2);
+
+ ASSERT_EQ(3u, client.returned_resources().size());
+ cc::ResourceId returned_ids[3];
+ for (size_t i = 0; i < 3; ++i) {
+ returned_ids[i] = client.returned_resources()[i].id;
+ }
+ EXPECT_THAT(returned_ids,
+ testing::WhenSorted(testing::ElementsAreArray(ids)));
+
+ support->EvictCurrentSurface();
+}
+
+TEST_F(SurfaceAggregatorWithResourcesTest, TakeInvalidResources) {
+ cc::FakeCompositorFrameSinkSupportClient client;
+ auto support = CompositorFrameSinkSupport::Create(
+ &client, &manager_, kArbitraryRootFrameSinkId, kRootIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_surface_id(7u, base::UnguessableToken::Create());
+ SurfaceId surface_id(support->frame_sink_id(), local_surface_id);
+
+ cc::CompositorFrame frame = cc::test::MakeCompositorFrame();
+ cc::TransferableResource resource;
+ resource.id = 11;
+ // ResourceProvider is software but resource is not, so it should be
+ // ignored.
+ resource.is_software = false;
+ frame.resource_list.push_back(resource);
+ support->SubmitCompositorFrame(local_surface_id, std::move(frame));
+
+ cc::CompositorFrame returned_frame = aggregator_->Aggregate(surface_id);
+
+ // Nothing should be available to be returned yet.
+ EXPECT_TRUE(client.returned_resources().empty());
+
+ SubmitCompositorFrameWithResources(NULL, 0, true, SurfaceId(), support.get(),
+ surface_id);
+ ASSERT_EQ(1u, client.returned_resources().size());
+ EXPECT_EQ(11u, client.returned_resources()[0].id);
+
+ support->EvictCurrentSurface();
+}
+
+TEST_F(SurfaceAggregatorWithResourcesTest, TwoSurfaces) {
+ cc::FakeCompositorFrameSinkSupportClient client;
+ auto support1 = CompositorFrameSinkSupport::Create(
+ &client, &manager_, FrameSinkId(1, 1), kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ auto support2 = CompositorFrameSinkSupport::Create(
+ &client, &manager_, FrameSinkId(2, 2), kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_frame1_id(7u, base::UnguessableToken::Create());
+ SurfaceId surface1_id(support1->frame_sink_id(), local_frame1_id);
+
+ LocalSurfaceId local_frame2_id(8u, base::UnguessableToken::Create());
+ SurfaceId surface2_id(support2->frame_sink_id(), local_frame2_id);
+
+ cc::ResourceId ids[] = {11, 12, 13};
+ SubmitCompositorFrameWithResources(ids, arraysize(ids), true, SurfaceId(),
+ support1.get(), surface1_id);
+ cc::ResourceId ids2[] = {14, 15, 16};
+ SubmitCompositorFrameWithResources(ids2, arraysize(ids2), true, SurfaceId(),
+ support2.get(), surface2_id);
+
+ cc::CompositorFrame frame = aggregator_->Aggregate(surface1_id);
+
+ SubmitCompositorFrameWithResources(NULL, 0, true, SurfaceId(), support1.get(),
+ surface1_id);
+
+ // Nothing should be available to be returned yet.
+ EXPECT_TRUE(client.returned_resources().empty());
+
+ frame = aggregator_->Aggregate(surface2_id);
+
+ // surface1_id wasn't referenced, so its resources should be returned.
+ ASSERT_EQ(3u, client.returned_resources().size());
+ cc::ResourceId returned_ids[3];
+ for (size_t i = 0; i < 3; ++i) {
+ returned_ids[i] = client.returned_resources()[i].id;
+ }
+ EXPECT_THAT(returned_ids,
+ testing::WhenSorted(testing::ElementsAreArray(ids)));
+ EXPECT_EQ(3u, resource_provider_->num_resources());
+
+ support1->EvictCurrentSurface();
+ support2->EvictCurrentSurface();
+}
+
+// Ensure that aggregator completely ignores Surfaces that reference invalid
+// resources.
+TEST_F(SurfaceAggregatorWithResourcesTest, InvalidChildSurface) {
+ auto root_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryRootFrameSinkId, kRootIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ auto middle_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryMiddleFrameSinkId, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ auto child_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryFrameSinkId1, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId root_local_surface_id(7u, kArbitraryToken);
+ SurfaceId root_surface_id(root_support->frame_sink_id(),
+ root_local_surface_id);
+ LocalSurfaceId middle_local_surface_id(8u, kArbitraryToken);
+ SurfaceId middle_surface_id(middle_support->frame_sink_id(),
+ middle_local_surface_id);
+ LocalSurfaceId child_local_surface_id(9u, kArbitraryToken);
+ SurfaceId child_surface_id(child_support->frame_sink_id(),
+ child_local_surface_id);
+
+ cc::ResourceId ids[] = {14, 15, 16};
+ SubmitCompositorFrameWithResources(ids, arraysize(ids), true, SurfaceId(),
+ child_support.get(), child_surface_id);
+
+ cc::ResourceId ids2[] = {17, 18, 19};
+ SubmitCompositorFrameWithResources(ids2, arraysize(ids2), false,
+ child_surface_id, middle_support.get(),
+ middle_surface_id);
+
+ cc::ResourceId ids3[] = {20, 21, 22};
+ SubmitCompositorFrameWithResources(ids3, arraysize(ids3), true,
+ middle_surface_id, root_support.get(),
+ root_surface_id);
+
+ cc::CompositorFrame frame;
+ frame = aggregator_->Aggregate(root_surface_id);
+
+ auto* pass_list = &frame.render_pass_list;
+ ASSERT_EQ(1u, pass_list->size());
+ EXPECT_EQ(1u, pass_list->back()->shared_quad_state_list.size());
+ EXPECT_EQ(3u, pass_list->back()->quad_list.size());
+ SubmitCompositorFrameWithResources(ids2, arraysize(ids), true,
+ child_surface_id, middle_support.get(),
+ middle_surface_id);
+
+ frame = aggregator_->Aggregate(root_surface_id);
+
+ pass_list = &frame.render_pass_list;
+ ASSERT_EQ(1u, pass_list->size());
+ EXPECT_EQ(3u, pass_list->back()->shared_quad_state_list.size());
+ EXPECT_EQ(9u, pass_list->back()->quad_list.size());
+
+ root_support->EvictCurrentSurface();
+ middle_support->EvictCurrentSurface();
+ child_support->EvictCurrentSurface();
+}
+
+TEST_F(SurfaceAggregatorWithResourcesTest, SecureOutputTexture) {
+ auto support1 = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, FrameSinkId(1, 1), kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ auto support2 = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, FrameSinkId(2, 2), kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_frame1_id(7u, base::UnguessableToken::Create());
+ SurfaceId surface1_id(support1->frame_sink_id(), local_frame1_id);
+
+ LocalSurfaceId local_frame2_id(8u, base::UnguessableToken::Create());
+ SurfaceId surface2_id(support2->frame_sink_id(), local_frame2_id);
+
+ cc::ResourceId ids[] = {11, 12, 13};
+ SubmitCompositorFrameWithResources(ids, arraysize(ids), true, SurfaceId(),
+ support1.get(), surface1_id);
+
+ cc::CompositorFrame frame = aggregator_->Aggregate(surface1_id);
+
+ auto* render_pass = frame.render_pass_list.back().get();
+
+ EXPECT_EQ(cc::DrawQuad::TEXTURE_CONTENT,
+ render_pass->quad_list.back()->material);
+
+ {
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(1, gfx::Rect(0, 0, 20, 20), gfx::Rect(), gfx::Transform());
+ auto* sqs = pass->CreateAndAppendSharedQuadState();
+ sqs->opacity = 1.f;
+ auto* surface_quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
+
+ surface_quad->SetNew(sqs, gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1),
+ surface1_id, cc::SurfaceDrawQuadType::PRIMARY,
+ nullptr);
+ pass->copy_requests.push_back(cc::CopyOutputRequest::CreateEmptyRequest());
+
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+ frame.render_pass_list.push_back(std::move(pass));
+
+ support2->SubmitCompositorFrame(local_frame2_id, std::move(frame));
+ }
+
+ frame = aggregator_->Aggregate(surface2_id);
+ EXPECT_EQ(1u, frame.render_pass_list.size());
+ render_pass = frame.render_pass_list.front().get();
+
+ // Parent has copy request, so texture should not be drawn.
+ EXPECT_EQ(cc::DrawQuad::SOLID_COLOR, render_pass->quad_list.back()->material);
+
+ frame = aggregator_->Aggregate(surface2_id);
+ EXPECT_EQ(1u, frame.render_pass_list.size());
+ render_pass = frame.render_pass_list.front().get();
+
+ // Copy request has been executed earlier, so texture should be drawn.
+ EXPECT_EQ(cc::DrawQuad::TEXTURE_CONTENT,
+ render_pass->quad_list.front()->material);
+
+ aggregator_->set_output_is_secure(false);
+
+ frame = aggregator_->Aggregate(surface2_id);
+ render_pass = frame.render_pass_list.back().get();
+
+ // Output is insecure, so texture should be drawn.
+ EXPECT_EQ(cc::DrawQuad::SOLID_COLOR, render_pass->quad_list.back()->material);
+
+ support1->EvictCurrentSurface();
+ support2->EvictCurrentSurface();
+}
+
+// Ensure that the render passes have correct color spaces.
+TEST_F(SurfaceAggregatorValidSurfaceTest, ColorSpaceTest) {
+ Quad quads[][2] = {{Quad::SolidColorQuad(SK_ColorWHITE),
+ Quad::SolidColorQuad(SK_ColorLTGRAY)},
+ {Quad::SolidColorQuad(SK_ColorGRAY),
+ Quad::SolidColorQuad(SK_ColorDKGRAY)}};
+ Pass passes[] = {Pass(quads[0], arraysize(quads[0]), 2),
+ Pass(quads[1], arraysize(quads[1]), 1)};
+ gfx::ColorSpace color_space1 = gfx::ColorSpace::CreateXYZD50();
+ gfx::ColorSpace color_space2 = gfx::ColorSpace::CreateSRGB();
+ gfx::ColorSpace color_space3 = gfx::ColorSpace::CreateSCRGBLinear();
+
+ SubmitCompositorFrame(support_.get(), passes, arraysize(passes),
+ root_local_surface_id_);
+
+ SurfaceId surface_id(support_->frame_sink_id(), root_local_surface_id_);
+
+ cc::CompositorFrame aggregated_frame;
+ aggregator_.SetOutputColorSpace(color_space1, color_space1);
+ aggregated_frame = aggregator_.Aggregate(surface_id);
+ EXPECT_EQ(2u, aggregated_frame.render_pass_list.size());
+ EXPECT_EQ(color_space1, aggregated_frame.render_pass_list[0]->color_space);
+ EXPECT_EQ(color_space1, aggregated_frame.render_pass_list[1]->color_space);
+
+ aggregator_.SetOutputColorSpace(color_space2, color_space2);
+ aggregated_frame = aggregator_.Aggregate(surface_id);
+ EXPECT_EQ(2u, aggregated_frame.render_pass_list.size());
+ EXPECT_EQ(color_space2, aggregated_frame.render_pass_list[0]->color_space);
+ EXPECT_EQ(color_space2, aggregated_frame.render_pass_list[1]->color_space);
+
+ aggregator_.SetOutputColorSpace(color_space1, color_space3);
+ aggregated_frame = aggregator_.Aggregate(surface_id);
+ EXPECT_EQ(3u, aggregated_frame.render_pass_list.size());
+ EXPECT_EQ(color_space1, aggregated_frame.render_pass_list[0]->color_space);
+ EXPECT_EQ(color_space1, aggregated_frame.render_pass_list[1]->color_space);
+ EXPECT_EQ(color_space3, aggregated_frame.render_pass_list[2]->color_space);
+}
+
+// Tests that has_damage_from_contributing_content is aggregated correctly from
+// child surface quads.
+TEST_F(SurfaceAggregatorValidSurfaceTest, HasDamageByChangingChildSurface) {
+ Quad child_surface_quads[] = {Quad::RenderPassQuad(1)};
+ Pass child_surface_passes[] = {
+ Pass(child_surface_quads, arraysize(child_surface_quads), 1)};
+
+ cc::CompositorFrame child_surface_frame =
+ cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_surface_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_surface_passes, arraysize(child_surface_passes));
+
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_surface_frame));
+
+ Quad root_surface_quads[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+ Pass root_passes[] = {
+ Pass(root_surface_quads, arraysize(root_surface_quads), 1)};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ // On first frame there is no existing cache texture to worry about re-using,
+ // so we don't worry what this bool is set to.
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ // No Surface changed, so no damage should be given.
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ EXPECT_FALSE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ }
+
+ // Change child_frame with damage should set the flag.
+ {
+ cc::CompositorFrame child_surface_frame =
+ cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_surface_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_surface_passes, arraysize(child_surface_passes));
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_surface_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ // True for new child_frame with damage.
+ EXPECT_TRUE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ }
+
+ // Change child_frame without damage should not set the flag.
+ {
+ cc::CompositorFrame child_surface_frame =
+ cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_surface_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_surface_passes, arraysize(child_surface_passes));
+ child_surface_frame.render_pass_list[0]->damage_rect = gfx::Rect();
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_surface_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ // False for new child_frame without damage.
+ EXPECT_FALSE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ }
+}
+
+// Tests that has_damage_from_contributing_content is aggregated correctly from
+// grand child surface quads.
+TEST_F(SurfaceAggregatorValidSurfaceTest,
+ HasDamageByChangingGrandChildSurface) {
+ auto grand_child_support = CompositorFrameSinkSupport::Create(
+ nullptr, &manager_, kArbitraryMiddleFrameSinkId, kChildIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+
+ Quad child_surface_quads[] = {Quad::RenderPassQuad(1)};
+ Pass child_surface_passes[] = {
+ Pass(child_surface_quads, arraysize(child_surface_quads), 1)};
+
+ cc::CompositorFrame child_surface_frame =
+ cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_surface_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_surface_passes, arraysize(child_surface_passes));
+
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_surface_frame));
+
+ Quad root_surface_quads[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+ Pass root_passes[] = {
+ Pass(root_surface_quads, arraysize(root_surface_quads), 1)};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ // On first frame there is no existing cache texture to worry about re-using,
+ // so we don't worry what this bool is set to.
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ // No Surface changed, so no damage should be given.
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ EXPECT_FALSE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ }
+
+ // Add a grand_child_frame should cause damage.
+ Quad grand_child_quads[] = {Quad::RenderPassQuad(1)};
+ Pass grand_child_passes[] = {
+ Pass(grand_child_quads, arraysize(grand_child_quads), 1)};
+ LocalSurfaceId grand_child_local_surface_id = allocator_.GenerateId();
+ SurfaceId grand_child_surface_id(grand_child_support->frame_sink_id(),
+ grand_child_local_surface_id);
+ {
+ cc::CompositorFrame grand_child_frame =
+ cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&grand_child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ grand_child_passes, arraysize(grand_child_passes));
+
+ grand_child_support->SubmitCompositorFrame(grand_child_local_surface_id,
+ std::move(grand_child_frame));
+
+ Quad new_child_surface_quads[] = {
+ child_surface_quads[0],
+ Quad::SurfaceQuad(grand_child_surface_id, InvalidSurfaceId(), 1.f)};
+ Pass new_child_surface_passes[] = {
+ Pass(new_child_surface_quads, arraysize(new_child_surface_quads), 1)};
+ child_surface_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_surface_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ new_child_surface_passes, arraysize(new_child_surface_passes));
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_surface_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ // True for new grand_child_frame.
+ EXPECT_TRUE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ }
+
+ // No Surface changed, so no damage should be given.
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ EXPECT_FALSE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ }
+
+ // Change grand_child_frame with damage should set the flag.
+ {
+ cc::CompositorFrame grand_child_frame =
+ cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&grand_child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ grand_child_passes, arraysize(grand_child_passes));
+ grand_child_support->SubmitCompositorFrame(grand_child_local_surface_id,
+ std::move(grand_child_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ // True for new grand_child_frame with damage.
+ EXPECT_TRUE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ }
+
+ // Change grand_child_frame without damage should not set the flag.
+ {
+ cc::CompositorFrame grand_child_frame =
+ cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&grand_child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ grand_child_passes, arraysize(grand_child_passes));
+ grand_child_frame.render_pass_list[0]->damage_rect = gfx::Rect();
+ grand_child_support->SubmitCompositorFrame(grand_child_local_surface_id,
+ std::move(grand_child_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ // False for new grand_child_frame without damage.
+ EXPECT_FALSE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ }
+
+ grand_child_support->EvictCurrentSurface();
+}
+
+// Tests that has_damage_from_contributing_content is aggregated correctly from
+// render pass quads.
+TEST_F(SurfaceAggregatorValidSurfaceTest, HasDamageFromRenderPassQuads) {
+ Quad child_quads[] = {Quad::RenderPassQuad(1)};
+ Pass child_passes[] = {Pass(child_quads, arraysize(child_quads), 1)};
+
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_passes, arraysize(child_passes));
+
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+
+ Quad root_surface_quads[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+ Quad root_render_pass_quads[] = {Quad::RenderPassQuad(1)};
+
+ Pass root_passes[] = {
+ Pass(root_surface_quads, arraysize(root_surface_quads), 1),
+ Pass(root_render_pass_quads, arraysize(root_render_pass_quads), 2)};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ // On first frame there is no existing cache texture to worry about re-using,
+ // so we don't worry what this bool is set to.
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(2u, aggregated_pass_list.size());
+
+ // No Surface changed, so no damage should be given.
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ EXPECT_FALSE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ EXPECT_FALSE(aggregated_frame.render_pass_list[1]
+ ->has_damage_from_contributing_content);
+ }
+
+ // Changing child_frame should damage both render_pass.
+ {
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_passes, arraysize(child_passes));
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ // True for new child_frame.
+ EXPECT_TRUE(aggregated_frame.render_pass_list[0]
+ ->has_damage_from_contributing_content);
+ EXPECT_TRUE(aggregated_frame.render_pass_list[1]
+ ->has_damage_from_contributing_content);
+ }
+}
+
+// Tests that the first frame damage_rect of a cached render pass should be
+// fully damaged.
+TEST_F(SurfaceAggregatorValidSurfaceTest, DamageRectOfCachedRenderPass) {
+ int pass_id[] = {1, 2};
+ Quad root_quads[][1] = {
+ {Quad::SolidColorQuad(SK_ColorGREEN)}, {Quad::RenderPassQuad(pass_id[0])},
+ };
+ Pass root_passes[] = {
+ Pass(root_quads[0], arraysize(root_quads[0]), pass_id[0]),
+ Pass(root_quads[1], arraysize(root_quads[1]), pass_id[1])};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(2u, aggregated_pass_list.size());
+
+ // The root surface was enqueued without being aggregated once, so it should
+ // be treated as completely damaged.
+ EXPECT_TRUE(
+ aggregated_pass_list[0]->damage_rect.Contains(gfx::Rect(SurfaceSize())));
+ EXPECT_TRUE(
+ aggregated_pass_list[1]->damage_rect.Contains(gfx::Rect(SurfaceSize())));
+
+ // For offscreen render pass, only the visible area is damaged.
+ {
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ root_passes, arraysize(root_passes));
+
+ auto* nonroot_pass = root_frame.render_pass_list[0].get();
+ nonroot_pass->transform_to_root_target.Translate(8, 0);
+
+ gfx::Rect root_pass_damage = gfx::Rect(0, 0, 10, 10);
+ auto* root_pass = root_frame.render_pass_list[1].get();
+ root_pass->damage_rect = root_pass_damage;
+ auto* root_pass_sqs = root_pass->shared_quad_state_list.front();
+ root_pass_sqs->quad_to_target_transform.Translate(8, 0);
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ // Only the visible area is damaged.
+ EXPECT_EQ(gfx::Rect(0, 0, 2, 10), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(root_pass_damage, aggregated_pass_list[1]->damage_rect);
+ }
+
+ // For offscreen cached render pass, should have full damage.
+ {
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ root_passes, arraysize(root_passes));
+
+ auto* nonroot_pass = root_frame.render_pass_list[0].get();
+ nonroot_pass->transform_to_root_target.Translate(8, 0);
+ nonroot_pass->cache_render_pass = true;
+
+ gfx::Rect root_pass_damage = gfx::Rect(0, 0, 10, 10);
+ auto* root_pass = root_frame.render_pass_list[1].get();
+ root_pass->damage_rect = root_pass_damage;
+ auto* root_pass_sqs = root_pass->shared_quad_state_list.front();
+ root_pass_sqs->quad_to_target_transform.Translate(8, 0);
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ // Should have full damage.
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(root_pass_damage, aggregated_pass_list[1]->damage_rect);
+ }
+}
+
+// Tests that the first frame damage_rect of cached render pass of a child
+// surface should be fully damaged.
+TEST_F(SurfaceAggregatorValidSurfaceTest,
+ DamageRectOfCachedRenderPassInChildSurface) {
+ int pass_id[] = {1, 2};
+ Quad child_quads[][1] = {
+ {Quad::SolidColorQuad(SK_ColorGREEN)}, {Quad::RenderPassQuad(pass_id[0])},
+ };
+ Pass child_passes[] = {
+ Pass(child_quads[0], arraysize(child_quads[0]), pass_id[0]),
+ Pass(child_quads[1], arraysize(child_quads[1]), pass_id[1])};
+
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_passes, arraysize(child_passes));
+
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+
+ Quad root_surface_quads[] = {
+ Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)};
+
+ Pass root_passes[] = {
+ Pass(root_surface_quads, arraysize(root_surface_quads), 1)};
+
+ cc::CompositorFrame root_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&root_frame.render_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ support_->SubmitCompositorFrame(root_local_surface_id_,
+ std::move(root_frame));
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(2u, aggregated_pass_list.size());
+
+ // The root surface was enqueued without being aggregated once, so it should
+ // be treated as completely damaged.
+ EXPECT_TRUE(
+ aggregated_pass_list[0]->damage_rect.Contains(gfx::Rect(SurfaceSize())));
+ EXPECT_TRUE(
+ aggregated_pass_list[1]->damage_rect.Contains(gfx::Rect(SurfaceSize())));
+
+ // For offscreen render pass, only the visible area is damaged.
+ {
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_passes, arraysize(child_passes));
+
+ auto* child_nonroot_pass = child_frame.render_pass_list[0].get();
+ child_nonroot_pass->transform_to_root_target.Translate(8, 0);
+
+ gfx::Rect child_root_pass_damage = gfx::Rect(0, 0, 10, 10);
+ auto* child_root_pass = child_frame.render_pass_list[1].get();
+ child_root_pass->damage_rect = child_root_pass_damage;
+ auto* child_root_pass_sqs = child_root_pass->shared_quad_state_list.front();
+ child_root_pass_sqs->quad_to_target_transform.Translate(8, 0);
+
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ // Only the visible area is damaged.
+ EXPECT_EQ(gfx::Rect(0, 0, 2, 10), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(child_root_pass_damage, aggregated_pass_list[1]->damage_rect);
+ }
+
+ // For offscreen cached render pass, should have full damage.
+ {
+ cc::CompositorFrame child_frame = cc::test::MakeEmptyCompositorFrame();
+ AddPasses(&child_frame.render_pass_list, gfx::Rect(SurfaceSize()),
+ child_passes, arraysize(child_passes));
+
+ auto* child_nonroot_pass = child_frame.render_pass_list[0].get();
+ child_nonroot_pass->transform_to_root_target.Translate(8, 0);
+ child_nonroot_pass->cache_render_pass = true;
+
+ gfx::Rect child_root_pass_damage = gfx::Rect(0, 0, 10, 10);
+ auto* child_root_pass = child_frame.render_pass_list[1].get();
+ child_root_pass->damage_rect = child_root_pass_damage;
+ auto* child_root_pass_sqs = child_root_pass->shared_quad_state_list.front();
+ child_root_pass_sqs->quad_to_target_transform.Translate(8, 0);
+
+ child_support_->SubmitCompositorFrame(child_local_surface_id,
+ std::move(child_frame));
+
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ // Should have full damage.
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(child_root_pass_damage, aggregated_pass_list[1]->damage_rect);
+ }
+}
+
+// Tests that quads outside the damage rect are not ignored for cached render
+// pass.
+TEST_F(SurfaceAggregatorPartialSwapTest, NotIgnoreOutsideForCachedRenderPass) {
+ LocalSurfaceId child_local_surface_id = allocator_.GenerateId();
+ SurfaceId child_surface_id(child_support_->frame_sink_id(),
+ child_local_surface_id);
+ // The child surface has two quads, one with a visible rect of 15,15 6x6 and
+ // the other other with a visible rect of 10,10 2x2 (relative to root target
+ // space).
+ {
+ int pass_id[] = {1, 2};
+ Quad child_quads[][1] = {
+ {Quad::SolidColorQuad(SK_ColorGREEN)},
+ {Quad::RenderPassQuad(pass_id[0])},
+ };
+ Pass child_passes[] = {
+ Pass(child_quads[0], arraysize(child_quads[0]), pass_id[0]),
+ Pass(child_quads[1], arraysize(child_quads[1]), pass_id[1])};
+
+ cc::RenderPassList child_pass_list;
+ AddPasses(&child_pass_list, gfx::Rect(SurfaceSize()), child_passes,
+ arraysize(child_passes));
+
+ child_pass_list[0]->quad_list.ElementAt(0)->visible_rect =
+ gfx::Rect(1, 1, 3, 3);
+ auto* child_sqs = child_pass_list[0]->shared_quad_state_list.ElementAt(0u);
+ child_sqs->quad_to_target_transform.Translate(3, 3);
+ child_sqs->quad_to_target_transform.Scale(2, 2);
+
+ child_pass_list[0]->cache_render_pass = true;
+
+ child_pass_list[1]->quad_list.ElementAt(0)->visible_rect =
+ gfx::Rect(0, 0, 2, 2);
+
+ SubmitPassListAsFrame(child_support_.get(), child_local_surface_id,
+ &child_pass_list);
+ }
+
+ {
+ int pass_id[] = {1, 2};
+ Quad root_quads[][1] = {
+ {Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)},
+ {Quad::RenderPassQuad(pass_id[0])},
+ };
+ Pass root_passes[] = {
+ Pass(root_quads[0], arraysize(root_quads[0]), pass_id[0]),
+ Pass(root_quads[1], arraysize(root_quads[1]), pass_id[1])};
+
+ cc::RenderPassList root_pass_list;
+ AddPasses(&root_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ auto* root_pass = root_pass_list[1].get();
+ root_pass->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(10, 10);
+ root_pass->damage_rect = gfx::Rect(0, 0, 1, 1);
+
+ SubmitPassListAsFrame(support_.get(), root_local_surface_id_,
+ &root_pass_list);
+ }
+
+ SurfaceId root_surface_id(support_->frame_sink_id(), root_local_surface_id_);
+ cc::CompositorFrame aggregated_frame = aggregator_.Aggregate(root_surface_id);
+
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+
+ // Damage rect for first aggregation should contain entire root surface.
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[2]->damage_rect);
+ EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+
+ // Test should not ignore outside for cached render pass.
+ // Create a root surface with a smaller damage rect.
+ {
+ int pass_id[] = {1, 2};
+ Quad root_quads[][1] = {
+ {Quad::SurfaceQuad(child_surface_id, InvalidSurfaceId(), 1.f)},
+ {Quad::RenderPassQuad(pass_id[0])},
+ };
+ Pass root_passes[] = {
+ Pass(root_quads[0], arraysize(root_quads[0]), pass_id[0]),
+ Pass(root_quads[1], arraysize(root_quads[1]), pass_id[1])};
+
+ cc::RenderPassList root_pass_list;
+ AddPasses(&root_pass_list, gfx::Rect(SurfaceSize()), root_passes,
+ arraysize(root_passes));
+
+ auto* root_pass = root_pass_list[1].get();
+ root_pass->shared_quad_state_list.front()
+ ->quad_to_target_transform.Translate(10, 10);
+ root_pass->damage_rect = gfx::Rect(10, 10, 2, 2);
+ SubmitPassListAsFrame(support_.get(), root_local_surface_id_,
+ &root_pass_list);
+ }
+
+ {
+ cc::CompositorFrame aggregated_frame =
+ aggregator_.Aggregate(root_surface_id);
+ const auto& aggregated_pass_list = aggregated_frame.render_pass_list;
+
+ ASSERT_EQ(3u, aggregated_pass_list.size());
+
+ // The first quad is a cached render pass, should be included and fully
+ // damaged.
+ EXPECT_EQ(gfx::Rect(1, 1, 3, 3),
+ aggregated_pass_list[0]->quad_list.back()->visible_rect);
+ EXPECT_EQ(gfx::Rect(0, 0, 2, 2),
+ aggregated_pass_list[1]->quad_list.back()->visible_rect);
+ EXPECT_EQ(gfx::Rect(SurfaceSize()), aggregated_pass_list[0]->damage_rect);
+ EXPECT_EQ(gfx::Rect(10, 10, 2, 2), aggregated_pass_list[1]->damage_rect);
+ EXPECT_EQ(gfx::Rect(10, 10, 2, 2), aggregated_pass_list[2]->damage_rect);
+ EXPECT_EQ(1u, aggregated_pass_list[0]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[1]->quad_list.size());
+ EXPECT_EQ(1u, aggregated_pass_list[2]->quad_list.size());
+ }
+}
+
+} // namespace
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/DEPS b/chromium/components/viz/service/display_embedder/DEPS
new file mode 100644
index 00000000000..f97559d9f01
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/DEPS
@@ -0,0 +1,32 @@
+include_rules = [
+ "+cc/base",
+ "+cc/ipc",
+ "+cc/output",
+ "+cc/resources",
+ "+cc/scheduler",
+ "+cc/surfaces",
+ "+gpu/GLES2",
+ "+gpu/command_buffer/client",
+ "+gpu/command_buffer/common",
+ "+gpu/command_buffer/service",
+ "+gpu/ipc/client",
+ "+gpu/ipc/common",
+ "+gpu/ipc/in_process_command_buffer.h",
+ "+gpu/ipc/service",
+ "+mojo/public/cpp/bindings",
+ "+mojo/public/cpp/system",
+ "+third_party/skia",
+ "+ui/display",
+ "+ui/gfx",
+ "+ui/gfx/geometry",
+ "+ui/gl",
+ "+ui/latency",
+ "+ui/ozone/public",
+]
+
+specific_include_rules = {
+ ".*_unittest\.cc": [
+ "+cc/test",
+ "+third_party/khronos/GLES2",
+ ],
+}
diff --git a/chromium/components/viz/service/display_embedder/OWNERS b/chromium/components/viz/service/display_embedder/OWNERS
new file mode 100644
index 00000000000..68de279ade5
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/OWNERS
@@ -0,0 +1,6 @@
+piman@chromium.org
+danakj@chromium.org
+jbauman@chromium.org
+ccameron@chromium.org
+
+# COMPONENT: Internals>Compositing
diff --git a/chromium/components/viz/service/display_embedder/README.md b/chromium/components/viz/service/display_embedder/README.md
new file mode 100644
index 00000000000..f7f83c6f8a4
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/README.md
@@ -0,0 +1,6 @@
+# display_embedder
+
+This directory contains the implementation used to embed a Display Compositor
+for a specific platform. That includes code for managing presentation of the
+Display Compositor's output to the user.
+
diff --git a/chromium/components/viz/service/display_embedder/buffer_queue.cc b/chromium/components/viz/service/display_embedder/buffer_queue.cc
new file mode 100644
index 00000000000..8c59cf2f5e9
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/buffer_queue.cc
@@ -0,0 +1,310 @@
+// Copyright 2014 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/display_embedder/buffer_queue.h"
+
+#include "base/containers/adapters.h"
+#include "base/memory/ptr_util.h"
+#include "build/build_config.h"
+#include "components/viz/common/gl_helper.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
+#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/display/types/display_snapshot.h"
+#include "ui/gfx/gpu_memory_buffer.h"
+#include "ui/gfx/skia_util.h"
+
+namespace viz {
+
+BufferQueue::BufferQueue(gpu::gles2::GLES2Interface* gl,
+ uint32_t texture_target,
+ uint32_t internal_format,
+ gfx::BufferFormat format,
+ GLHelper* gl_helper,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ gpu::SurfaceHandle surface_handle)
+ : gl_(gl),
+ fbo_(0),
+ allocated_count_(0),
+ texture_target_(texture_target),
+ internal_format_(internal_format),
+ format_(format),
+ gl_helper_(gl_helper),
+ gpu_memory_buffer_manager_(gpu_memory_buffer_manager),
+ surface_handle_(surface_handle) {
+ DCHECK(gpu::IsImageFormatCompatibleWithGpuMemoryBufferFormat(internal_format,
+ format_));
+}
+
+BufferQueue::~BufferQueue() {
+ FreeAllSurfaces();
+
+ if (fbo_)
+ gl_->DeleteFramebuffers(1, &fbo_);
+}
+
+void BufferQueue::Initialize() {
+ gl_->GenFramebuffers(1, &fbo_);
+}
+
+void BufferQueue::BindFramebuffer() {
+ gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_);
+
+ if (!current_surface_)
+ current_surface_ = GetNextSurface();
+
+ if (current_surface_) {
+ gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ texture_target_, current_surface_->texture, 0);
+ if (current_surface_->stencil) {
+ gl_->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, current_surface_->stencil);
+ }
+ }
+}
+
+void BufferQueue::CopyBufferDamage(int texture,
+ int source_texture,
+ const gfx::Rect& new_damage,
+ const gfx::Rect& old_damage) {
+ gl_helper_->CopySubBufferDamage(texture_target_, texture, source_texture,
+ SkRegion(gfx::RectToSkIRect(new_damage)),
+ SkRegion(gfx::RectToSkIRect(old_damage)));
+}
+
+void BufferQueue::UpdateBufferDamage(const gfx::Rect& damage) {
+ if (displayed_surface_)
+ displayed_surface_->damage.Union(damage);
+ for (auto& surface : available_surfaces_)
+ surface->damage.Union(damage);
+ for (auto& surface : in_flight_surfaces_) {
+ if (surface)
+ surface->damage.Union(damage);
+ }
+}
+
+void BufferQueue::SwapBuffers(const gfx::Rect& damage) {
+ if (damage.IsEmpty()) {
+ in_flight_surfaces_.push_back(nullptr);
+ return;
+ }
+
+ if (current_surface_) {
+ if (damage != gfx::Rect(size_)) {
+ // Copy damage from the most recently swapped buffer. In the event that
+ // the buffer was destroyed and failed to recreate, pick from the most
+ // recently available buffer.
+ uint32_t texture_id = 0;
+ for (auto& surface : base::Reversed(in_flight_surfaces_)) {
+ if (surface) {
+ texture_id = surface->texture;
+ break;
+ }
+ }
+ if (!texture_id && displayed_surface_)
+ texture_id = displayed_surface_->texture;
+
+ if (texture_id) {
+ CopyBufferDamage(current_surface_->texture, texture_id, damage,
+ current_surface_->damage);
+ }
+ }
+ current_surface_->damage = gfx::Rect();
+ }
+ UpdateBufferDamage(damage);
+ in_flight_surfaces_.push_back(std::move(current_surface_));
+ // Some things reset the framebuffer (CopySubBufferDamage, some GLRenderer
+ // paths), so ensure we restore it here.
+ gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_);
+}
+
+void BufferQueue::Reshape(const gfx::Size& size,
+ float scale_factor,
+ const gfx::ColorSpace& color_space,
+ bool use_stencil) {
+ if (size == size_ && color_space == color_space_ &&
+ use_stencil == use_stencil_)
+ return;
+#if !defined(OS_MACOSX)
+ // TODO(ccameron): This assert is being hit on Mac try jobs. Determine if that
+ // is cause for concern or if it is benign.
+ // http://crbug.com/524624
+ DCHECK(!current_surface_);
+#endif
+ size_ = size;
+ color_space_ = color_space;
+ use_stencil_ = use_stencil;
+
+ gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_);
+ gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ texture_target_, 0, 0);
+ gl_->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, 0);
+
+ FreeAllSurfaces();
+}
+
+void BufferQueue::RecreateBuffers() {
+ // We need to recreate the buffers, for whatever reason the old ones are not
+ // presentable on the device anymore.
+ // Unused buffers can be freed directly, they will be re-allocated as needed.
+ // Any in flight, current or displayed surface must be replaced.
+ available_surfaces_.clear();
+
+ // Recreate all in-flight surfaces and put the recreated copies in the queue.
+ for (auto& surface : in_flight_surfaces_)
+ surface = RecreateBuffer(std::move(surface));
+
+ current_surface_ = RecreateBuffer(std::move(current_surface_));
+ displayed_surface_ = RecreateBuffer(std::move(displayed_surface_));
+
+ if (current_surface_) {
+ // If we have a texture bound, we will need to re-bind it.
+ gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_);
+ gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ texture_target_, current_surface_->texture, 0);
+ }
+}
+
+std::unique_ptr<BufferQueue::AllocatedSurface> BufferQueue::RecreateBuffer(
+ std::unique_ptr<AllocatedSurface> surface) {
+ if (!surface)
+ return nullptr;
+
+ std::unique_ptr<AllocatedSurface> new_surface(GetNextSurface());
+ if (!new_surface)
+ return nullptr;
+
+ new_surface->damage = surface->damage;
+
+ // Copy the entire texture.
+ CopyBufferDamage(new_surface->texture, surface->texture, gfx::Rect(),
+ gfx::Rect(size_));
+ return new_surface;
+}
+
+void BufferQueue::PageFlipComplete() {
+ DCHECK(!in_flight_surfaces_.empty());
+ if (in_flight_surfaces_.front()) {
+ if (displayed_surface_)
+ available_surfaces_.push_back(std::move(displayed_surface_));
+ displayed_surface_ = std::move(in_flight_surfaces_.front());
+ }
+
+ in_flight_surfaces_.pop_front();
+}
+
+uint32_t BufferQueue::GetCurrentTextureId() const {
+ if (current_surface_)
+ return current_surface_->texture;
+
+ // Return in-flight or displayed surface texture if no surface is
+ // currently bound. This can happen when using overlays and surface
+ // damage is empty. Note: |in_flight_surfaces_| entries can be null
+ // as a result of calling FreeAllSurfaces().
+ for (auto& surface : base::Reversed(in_flight_surfaces_)) {
+ if (surface)
+ return surface->texture;
+ }
+
+ if (displayed_surface_)
+ return displayed_surface_->texture;
+
+ return 0;
+}
+
+void BufferQueue::FreeAllSurfaces() {
+ displayed_surface_.reset();
+ current_surface_.reset();
+ // This is intentionally not emptied since the swap buffers acks are still
+ // expected to arrive.
+ for (auto& surface : in_flight_surfaces_)
+ surface = nullptr;
+ available_surfaces_.clear();
+}
+
+void BufferQueue::FreeSurfaceResources(AllocatedSurface* surface) {
+ if (!surface->texture)
+ return;
+
+ gl_->BindTexture(texture_target_, surface->texture);
+ gl_->ReleaseTexImage2DCHROMIUM(texture_target_, surface->image);
+ gl_->DeleteTextures(1, &surface->texture);
+ gl_->DestroyImageCHROMIUM(surface->image);
+ if (surface->stencil)
+ gl_->DeleteRenderbuffers(1, &surface->stencil);
+ surface->buffer.reset();
+ allocated_count_--;
+}
+
+std::unique_ptr<BufferQueue::AllocatedSurface> BufferQueue::GetNextSurface() {
+ if (!available_surfaces_.empty()) {
+ std::unique_ptr<AllocatedSurface> surface =
+ std::move(available_surfaces_.back());
+ available_surfaces_.pop_back();
+ return surface;
+ }
+
+ GLuint texture;
+ gl_->GenTextures(1, &texture);
+
+ GLuint stencil = 0;
+ if (use_stencil_) {
+ gl_->GenRenderbuffers(1, &stencil);
+ gl_->BindRenderbuffer(GL_RENDERBUFFER, stencil);
+ gl_->RenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, size_.width(),
+ size_.height());
+ gl_->BindRenderbuffer(GL_RENDERBUFFER, 0);
+ }
+
+ // We don't want to allow anything more than triple buffering.
+ DCHECK_LT(allocated_count_, 4U);
+ std::unique_ptr<gfx::GpuMemoryBuffer> buffer(
+ gpu_memory_buffer_manager_->CreateGpuMemoryBuffer(
+ size_, format_, gfx::BufferUsage::SCANOUT, surface_handle_));
+ if (!buffer.get()) {
+ gl_->DeleteTextures(1, &texture);
+ DLOG(ERROR) << "Failed to allocate GPU memory buffer";
+ return nullptr;
+ }
+ buffer->SetColorSpaceForScanout(color_space_);
+
+ uint32_t id =
+ gl_->CreateImageCHROMIUM(buffer->AsClientBuffer(), size_.width(),
+ size_.height(), internal_format_);
+ if (!id) {
+ LOG(ERROR) << "Failed to allocate backing image surface";
+ gl_->DeleteTextures(1, &texture);
+ return nullptr;
+ }
+
+ allocated_count_++;
+ gl_->BindTexture(texture_target_, texture);
+ gl_->BindTexImage2DCHROMIUM(texture_target_, id);
+ return base::MakeUnique<AllocatedSurface>(this, std::move(buffer), texture,
+ id, stencil, gfx::Rect(size_));
+}
+
+BufferQueue::AllocatedSurface::AllocatedSurface(
+ BufferQueue* buffer_queue,
+ std::unique_ptr<gfx::GpuMemoryBuffer> buffer,
+ uint32_t texture,
+ uint32_t image,
+ uint32_t stencil,
+ const gfx::Rect& rect)
+ : buffer_queue(buffer_queue),
+ buffer(buffer.release()),
+ texture(texture),
+ image(image),
+ stencil(stencil),
+ damage(rect) {}
+
+BufferQueue::AllocatedSurface::~AllocatedSurface() {
+ buffer_queue->FreeSurfaceResources(this);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/buffer_queue.h b/chromium/components/viz/service/display_embedder/buffer_queue.h
new file mode 100644
index 00000000000..2ab4915857f
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/buffer_queue.h
@@ -0,0 +1,137 @@
+// Copyright 2014 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_DISPLAY_EMBEDDER_BUFFER_QUEUE_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_BUFFER_QUEUE_H_
+
+#include <stddef.h>
+
+#include <deque>
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/viz/service/viz_service_export.h"
+#include "gpu/ipc/common/surface_handle.h"
+#include "ui/gfx/buffer_types.h"
+#include "ui/gfx/color_space.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace gfx {
+class GpuMemoryBuffer;
+}
+
+namespace gpu {
+class GpuMemoryBufferManager;
+
+namespace gles2 {
+class GLES2Interface;
+}
+} // namespace gpu
+
+namespace viz {
+
+class GLHelper;
+
+// Provides a surface that manages its own buffers, backed by GpuMemoryBuffers
+// created using CHROMIUM_image. Double/triple buffering is implemented
+// internally. Doublebuffering occurs if PageFlipComplete is called before the
+// next BindFramebuffer call, otherwise it creates extra buffers.
+class VIZ_SERVICE_EXPORT BufferQueue {
+ public:
+ BufferQueue(gpu::gles2::GLES2Interface* gl,
+ uint32_t texture_target,
+ uint32_t internal_format,
+ gfx::BufferFormat format,
+ GLHelper* gl_helper,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ gpu::SurfaceHandle surface_handle);
+ virtual ~BufferQueue();
+
+ void Initialize();
+
+ void BindFramebuffer();
+ void SwapBuffers(const gfx::Rect& damage);
+ void PageFlipComplete();
+ void Reshape(const gfx::Size& size,
+ float scale_factor,
+ const gfx::ColorSpace& color_space,
+ bool use_stencil);
+ void RecreateBuffers();
+ uint32_t GetCurrentTextureId() const;
+
+ uint32_t fbo() const { return fbo_; }
+ uint32_t internal_format() const { return internal_format_; }
+ gfx::BufferFormat buffer_format() const { return format_; }
+
+ private:
+ friend class BufferQueueTest;
+ friend class AllocatedSurface;
+
+ struct VIZ_SERVICE_EXPORT AllocatedSurface {
+ AllocatedSurface(BufferQueue* buffer_queue,
+ std::unique_ptr<gfx::GpuMemoryBuffer> buffer,
+ uint32_t texture,
+ uint32_t image,
+ uint32_t stencil,
+ const gfx::Rect& rect);
+ ~AllocatedSurface();
+ BufferQueue* const buffer_queue;
+ std::unique_ptr<gfx::GpuMemoryBuffer> buffer;
+ const uint32_t texture;
+ const uint32_t image;
+ const uint32_t stencil;
+ gfx::Rect damage; // This is the damage for this frame from the previous.
+ };
+
+ void FreeAllSurfaces();
+
+ void FreeSurfaceResources(AllocatedSurface* surface);
+
+ // Copy everything that is in |copy_rect|, except for what is in
+ // |exclude_rect| from |source_texture| to |texture|.
+ virtual void CopyBufferDamage(int texture,
+ int source_texture,
+ const gfx::Rect& new_damage,
+ const gfx::Rect& old_damage);
+
+ void UpdateBufferDamage(const gfx::Rect& damage);
+
+ // Return a surface, available to be drawn into.
+ std::unique_ptr<AllocatedSurface> GetNextSurface();
+
+ std::unique_ptr<AllocatedSurface> RecreateBuffer(
+ std::unique_ptr<AllocatedSurface> surface);
+
+ gpu::gles2::GLES2Interface* const gl_;
+ gfx::Size size_;
+ gfx::ColorSpace color_space_;
+ bool use_stencil_ = false;
+ uint32_t fbo_;
+ size_t allocated_count_;
+ uint32_t texture_target_;
+ uint32_t internal_format_;
+ gfx::BufferFormat format_;
+ // This surface is currently bound. This may be nullptr if no surface has
+ // been bound, or if allocation failed at bind.
+ std::unique_ptr<AllocatedSurface> current_surface_;
+ // The surface currently on the screen, if any.
+ std::unique_ptr<AllocatedSurface> displayed_surface_;
+ // These are free for use, and are not nullptr.
+ std::vector<std::unique_ptr<AllocatedSurface>> available_surfaces_;
+ // These have been swapped but are not displayed yet. Entries of this deque
+ // may be nullptr, if they represent frames that have been destroyed.
+ std::deque<std::unique_ptr<AllocatedSurface>> in_flight_surfaces_;
+ GLHelper* gl_helper_;
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager_;
+ gpu::SurfaceHandle surface_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferQueue);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_BUFFER_QUEUE_H_
diff --git a/chromium/components/viz/service/display_embedder/buffer_queue_unittest.cc b/chromium/components/viz/service/display_embedder/buffer_queue_unittest.cc
new file mode 100644
index 00000000000..5f2179029fb
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/buffer_queue_unittest.cc
@@ -0,0 +1,704 @@
+// Copyright 2014 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/display_embedder/buffer_queue.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <set>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "build/build_config.h"
+#include "cc/test/test_context_provider.h"
+#include "cc/test/test_gpu_memory_buffer_manager.h"
+#include "cc/test/test_web_graphics_context_3d.h"
+#include "components/viz/common/gl_helper.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/display/types/display_snapshot.h"
+
+using ::testing::_;
+using ::testing::Expectation;
+using ::testing::Ne;
+using ::testing::Return;
+
+namespace viz {
+
+class StubGpuMemoryBufferImpl : public gfx::GpuMemoryBuffer {
+ public:
+ explicit StubGpuMemoryBufferImpl(size_t* set_color_space_count)
+ : set_color_space_count_(set_color_space_count) {}
+
+ // Overridden from gfx::GpuMemoryBuffer:
+ bool Map() override { return false; }
+ void* memory(size_t plane) override { return nullptr; }
+ void Unmap() override {}
+ gfx::Size GetSize() const override { return gfx::Size(); }
+ gfx::BufferFormat GetFormat() const override {
+ return gfx::BufferFormat::BGRX_8888;
+ }
+ int stride(size_t plane) const override { return 0; }
+ gfx::GpuMemoryBufferId GetId() const override {
+ return gfx::GpuMemoryBufferId(0);
+ }
+ void SetColorSpaceForScanout(const gfx::ColorSpace& color_space) override {
+ *set_color_space_count_ += 1;
+ }
+ gfx::GpuMemoryBufferHandle GetHandle() const override {
+ return gfx::GpuMemoryBufferHandle();
+ }
+ ClientBuffer AsClientBuffer() override {
+ return reinterpret_cast<ClientBuffer>(this);
+ }
+
+ size_t* set_color_space_count_;
+};
+
+class StubGpuMemoryBufferManager : public cc::TestGpuMemoryBufferManager {
+ public:
+ StubGpuMemoryBufferManager() : allocate_succeeds_(true) {}
+
+ size_t set_color_space_count() const { return set_color_space_count_; }
+
+ void set_allocate_succeeds(bool value) { allocate_succeeds_ = value; }
+
+ std::unique_ptr<gfx::GpuMemoryBuffer> CreateGpuMemoryBuffer(
+ const gfx::Size& size,
+ gfx::BufferFormat format,
+ gfx::BufferUsage usage,
+ gpu::SurfaceHandle surface_handle) override {
+ if (!surface_handle) {
+ return TestGpuMemoryBufferManager::CreateGpuMemoryBuffer(
+ size, format, usage, surface_handle);
+ }
+ if (allocate_succeeds_)
+ return base::WrapUnique<gfx::GpuMemoryBuffer>(
+ new StubGpuMemoryBufferImpl(&set_color_space_count_));
+ return nullptr;
+ }
+
+ private:
+ bool allocate_succeeds_;
+ size_t set_color_space_count_ = 0;
+};
+
+#if defined(OS_WIN)
+const gpu::SurfaceHandle kFakeSurfaceHandle =
+ reinterpret_cast<gpu::SurfaceHandle>(1);
+#else
+const gpu::SurfaceHandle kFakeSurfaceHandle = 1;
+#endif
+
+class MockBufferQueue : public BufferQueue {
+ public:
+ MockBufferQueue(gpu::gles2::GLES2Interface* gl,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ unsigned int target,
+ unsigned int internalformat)
+ : BufferQueue(gl,
+ target,
+ internalformat,
+ display::DisplaySnapshot::PrimaryFormat(),
+ nullptr,
+ gpu_memory_buffer_manager,
+ kFakeSurfaceHandle) {}
+ MOCK_METHOD4(CopyBufferDamage,
+ void(int, int, const gfx::Rect&, const gfx::Rect&));
+};
+
+class BufferQueueTest : public ::testing::Test {
+ public:
+ BufferQueueTest() : doublebuffering_(true), first_frame_(true) {}
+
+ void SetUp() override {
+ InitWithContext(cc::TestWebGraphicsContext3D::Create());
+ }
+
+ void InitWithContext(std::unique_ptr<cc::TestWebGraphicsContext3D> context) {
+ context_provider_ = cc::TestContextProvider::Create(std::move(context));
+ context_provider_->BindToCurrentThread();
+ gpu_memory_buffer_manager_.reset(new StubGpuMemoryBufferManager);
+ mock_output_surface_ = new MockBufferQueue(context_provider_->ContextGL(),
+ gpu_memory_buffer_manager_.get(),
+ GL_TEXTURE_2D, GL_RGB);
+ output_surface_.reset(mock_output_surface_);
+ output_surface_->Initialize();
+ }
+
+ unsigned current_surface() {
+ return output_surface_->current_surface_
+ ? output_surface_->current_surface_->image
+ : 0;
+ }
+ const std::vector<std::unique_ptr<BufferQueue::AllocatedSurface>>&
+ available_surfaces() {
+ return output_surface_->available_surfaces_;
+ }
+ std::deque<std::unique_ptr<BufferQueue::AllocatedSurface>>&
+ in_flight_surfaces() {
+ return output_surface_->in_flight_surfaces_;
+ }
+
+ const BufferQueue::AllocatedSurface* displayed_frame() {
+ return output_surface_->displayed_surface_.get();
+ }
+ const BufferQueue::AllocatedSurface* current_frame() {
+ return output_surface_->current_surface_.get();
+ }
+ const BufferQueue::AllocatedSurface* next_frame() {
+ return output_surface_->available_surfaces_.back().get();
+ }
+ const gfx::Size size() { return output_surface_->size_; }
+
+ int CountBuffers() {
+ int n = available_surfaces().size() + in_flight_surfaces().size() +
+ (displayed_frame() ? 1 : 0);
+ if (current_surface())
+ n++;
+ return n;
+ }
+
+ // Check that each buffer is unique if present.
+ void CheckUnique() {
+ std::set<unsigned> buffers;
+ EXPECT_TRUE(InsertUnique(&buffers, current_surface()));
+ if (displayed_frame())
+ EXPECT_TRUE(InsertUnique(&buffers, displayed_frame()->image));
+ for (auto& surface : available_surfaces())
+ EXPECT_TRUE(InsertUnique(&buffers, surface->image));
+ for (auto& surface : in_flight_surfaces()) {
+ if (surface)
+ EXPECT_TRUE(InsertUnique(&buffers, surface->image));
+ }
+ }
+
+ void SwapBuffers() {
+ output_surface_->SwapBuffers(gfx::Rect(output_surface_->size_));
+ }
+
+ void SendDamagedFrame(const gfx::Rect& damage) {
+ // We don't care about the GL-level implementation here, just how it uses
+ // damage rects.
+ output_surface_->BindFramebuffer();
+ output_surface_->SwapBuffers(damage);
+ if (doublebuffering_ || !first_frame_)
+ output_surface_->PageFlipComplete();
+ first_frame_ = false;
+ }
+
+ void SendFullFrame() { SendDamagedFrame(gfx::Rect(output_surface_->size_)); }
+
+ protected:
+ bool InsertUnique(std::set<unsigned>* set, unsigned value) {
+ if (!value)
+ return true;
+ if (set->find(value) != set->end())
+ return false;
+ set->insert(value);
+ return true;
+ }
+
+ scoped_refptr<cc::TestContextProvider> context_provider_;
+ std::unique_ptr<StubGpuMemoryBufferManager> gpu_memory_buffer_manager_;
+ std::unique_ptr<BufferQueue> output_surface_;
+ MockBufferQueue* mock_output_surface_;
+ bool doublebuffering_;
+ bool first_frame_;
+};
+
+namespace {
+const gfx::Size screen_size = gfx::Size(30, 30);
+const gfx::Rect screen_rect = gfx::Rect(screen_size);
+const gfx::Rect small_damage = gfx::Rect(gfx::Size(10, 10));
+const gfx::Rect large_damage = gfx::Rect(gfx::Size(20, 20));
+const gfx::Rect overlapping_damage = gfx::Rect(gfx::Size(5, 20));
+
+GLuint CreateImageDefault() {
+ static GLuint id = 0;
+ return ++id;
+}
+
+class MockedContext : public cc::TestWebGraphicsContext3D {
+ public:
+ MockedContext() {
+ ON_CALL(*this, createImageCHROMIUM(_, _, _, _))
+ .WillByDefault(testing::InvokeWithoutArgs(&CreateImageDefault));
+ }
+ MOCK_METHOD2(bindFramebuffer, void(GLenum, GLuint));
+ MOCK_METHOD2(bindTexture, void(GLenum, GLuint));
+ MOCK_METHOD2(bindTexImage2DCHROMIUM, void(GLenum, GLint));
+ MOCK_METHOD4(createImageCHROMIUM,
+ GLuint(ClientBuffer, GLsizei, GLsizei, GLenum));
+ MOCK_METHOD1(destroyImageCHROMIUM, void(GLuint));
+ MOCK_METHOD5(framebufferTexture2D,
+ void(GLenum, GLenum, GLenum, GLuint, GLint));
+};
+
+class BufferQueueMockedContextTest : public BufferQueueTest {
+ public:
+ void SetUp() override {
+ context_ = new MockedContext();
+ InitWithContext(std::unique_ptr<cc::TestWebGraphicsContext3D>(context_));
+ }
+
+ protected:
+ MockedContext* context_;
+};
+
+scoped_refptr<cc::TestContextProvider> CreateMockedContextProvider(
+ MockedContext** context) {
+ std::unique_ptr<MockedContext> owned_context(new MockedContext);
+ *context = owned_context.get();
+ scoped_refptr<cc::TestContextProvider> context_provider =
+ cc::TestContextProvider::Create(std::move(owned_context));
+ context_provider->BindToCurrentThread();
+ return context_provider;
+}
+
+std::unique_ptr<BufferQueue> CreateBufferQueue(
+ unsigned int target,
+ gpu::gles2::GLES2Interface* gl,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager) {
+ std::unique_ptr<BufferQueue> buffer_queue(new BufferQueue(
+ gl, target, GL_RGB, display::DisplaySnapshot::PrimaryFormat(), nullptr,
+ gpu_memory_buffer_manager, kFakeSurfaceHandle));
+ buffer_queue->Initialize();
+ return buffer_queue;
+}
+
+TEST(BufferQueueStandaloneTest, FboInitialization) {
+ MockedContext* context;
+ scoped_refptr<cc::TestContextProvider> context_provider =
+ CreateMockedContextProvider(&context);
+ std::unique_ptr<StubGpuMemoryBufferManager> gpu_memory_buffer_manager(
+ new StubGpuMemoryBufferManager);
+ std::unique_ptr<BufferQueue> output_surface =
+ CreateBufferQueue(GL_TEXTURE_2D, context_provider->ContextGL(),
+ gpu_memory_buffer_manager.get());
+
+ EXPECT_CALL(*context, bindFramebuffer(GL_FRAMEBUFFER, Ne(0U)));
+ ON_CALL(*context, framebufferTexture2D(_, _, _, _, _))
+ .WillByDefault(Return());
+
+ output_surface->Reshape(gfx::Size(10, 20), 1.0f, gfx::ColorSpace(), false);
+}
+
+TEST(BufferQueueStandaloneTest, FboBinding) {
+ GLenum targets[] = {GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE_ARB};
+ for (size_t i = 0; i < 2; ++i) {
+ GLenum target = targets[i];
+
+ MockedContext* context;
+ scoped_refptr<cc::TestContextProvider> context_provider =
+ CreateMockedContextProvider(&context);
+ std::unique_ptr<StubGpuMemoryBufferManager> gpu_memory_buffer_manager(
+ new StubGpuMemoryBufferManager);
+ std::unique_ptr<BufferQueue> output_surface = CreateBufferQueue(
+ target, context_provider->ContextGL(), gpu_memory_buffer_manager.get());
+
+ EXPECT_CALL(*context, bindTexture(target, Ne(0U)));
+ EXPECT_CALL(*context, destroyImageCHROMIUM(1));
+ Expectation image =
+ EXPECT_CALL(*context, createImageCHROMIUM(_, 0, 0, GL_RGB))
+ .WillOnce(Return(1));
+ Expectation fb =
+ EXPECT_CALL(*context, bindFramebuffer(GL_FRAMEBUFFER, Ne(0U)));
+ Expectation tex = EXPECT_CALL(*context, bindTexture(target, Ne(0U)));
+ Expectation bind_tex =
+ EXPECT_CALL(*context, bindTexImage2DCHROMIUM(target, 1))
+ .After(tex, image);
+ EXPECT_CALL(*context,
+ framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ target, Ne(0U), _))
+ .After(fb, bind_tex);
+
+ output_surface->BindFramebuffer();
+ }
+}
+
+TEST(BufferQueueStandaloneTest, CheckBoundFramebuffer) {
+ scoped_refptr<cc::TestContextProvider> context_provider =
+ cc::TestContextProvider::Create();
+ context_provider->BindToCurrentThread();
+ std::unique_ptr<StubGpuMemoryBufferManager> gpu_memory_buffer_manager;
+ std::unique_ptr<BufferQueue> output_surface;
+ gpu_memory_buffer_manager.reset(new StubGpuMemoryBufferManager);
+
+ std::unique_ptr<GLHelper> gl_helper;
+ gl_helper.reset(new GLHelper(context_provider->ContextGL(),
+ context_provider->ContextSupport()));
+
+ output_surface.reset(new BufferQueue(
+ context_provider->ContextGL(), GL_TEXTURE_2D, GL_RGB,
+ display::DisplaySnapshot::PrimaryFormat(), gl_helper.get(),
+ gpu_memory_buffer_manager.get(), kFakeSurfaceHandle));
+ output_surface->Initialize();
+ output_surface->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ // Trigger a sub-buffer copy to exercise all paths.
+ output_surface->BindFramebuffer();
+ output_surface->SwapBuffers(screen_rect);
+ output_surface->PageFlipComplete();
+ output_surface->BindFramebuffer();
+ output_surface->SwapBuffers(small_damage);
+
+ int current_fbo = 0;
+ context_provider->ContextGL()->GetIntegerv(GL_FRAMEBUFFER_BINDING,
+ &current_fbo);
+ EXPECT_EQ(static_cast<int>(output_surface->fbo()), current_fbo);
+}
+
+TEST_F(BufferQueueTest, PartialSwapReuse) {
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ ASSERT_TRUE(doublebuffering_);
+ EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, _, small_damage, screen_rect))
+ .Times(1);
+ EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, _, small_damage, small_damage))
+ .Times(1);
+ EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, _, large_damage, small_damage))
+ .Times(1);
+ SendFullFrame();
+ SendDamagedFrame(small_damage);
+ SendDamagedFrame(small_damage);
+ SendDamagedFrame(large_damage);
+ // Verify that the damage has propagated.
+ EXPECT_EQ(next_frame()->damage, large_damage);
+}
+
+TEST_F(BufferQueueTest, PartialSwapFullFrame) {
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ ASSERT_TRUE(doublebuffering_);
+ EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, _, small_damage, screen_rect))
+ .Times(1);
+ SendFullFrame();
+ SendDamagedFrame(small_damage);
+ SendFullFrame();
+ SendFullFrame();
+ EXPECT_EQ(next_frame()->damage, screen_rect);
+}
+
+TEST_F(BufferQueueTest, PartialSwapOverlapping) {
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ ASSERT_TRUE(doublebuffering_);
+ EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, _, small_damage, screen_rect))
+ .Times(1);
+ EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, _, overlapping_damage, small_damage))
+ .Times(1);
+
+ SendFullFrame();
+ SendDamagedFrame(small_damage);
+ SendDamagedFrame(overlapping_damage);
+ EXPECT_EQ(next_frame()->damage, overlapping_damage);
+}
+
+TEST_F(BufferQueueTest, MultipleBindCalls) {
+ // Check that multiple bind calls do not create or change surfaces.
+ output_surface_->BindFramebuffer();
+ EXPECT_EQ(1, CountBuffers());
+ unsigned int fb = current_surface();
+ output_surface_->BindFramebuffer();
+ EXPECT_EQ(1, CountBuffers());
+ EXPECT_EQ(fb, current_surface());
+}
+
+TEST_F(BufferQueueTest, CheckDoubleBuffering) {
+ // Check buffer flow through double buffering path.
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ EXPECT_EQ(0, CountBuffers());
+ output_surface_->BindFramebuffer();
+ EXPECT_EQ(1, CountBuffers());
+ EXPECT_NE(0U, current_surface());
+ EXPECT_FALSE(displayed_frame());
+ SwapBuffers();
+ EXPECT_EQ(1U, in_flight_surfaces().size());
+ output_surface_->PageFlipComplete();
+ EXPECT_EQ(0U, in_flight_surfaces().size());
+ EXPECT_TRUE(displayed_frame()->texture);
+ output_surface_->BindFramebuffer();
+ EXPECT_EQ(2, CountBuffers());
+ CheckUnique();
+ EXPECT_NE(0U, current_surface());
+ EXPECT_EQ(0U, in_flight_surfaces().size());
+ EXPECT_TRUE(displayed_frame()->texture);
+ SwapBuffers();
+ CheckUnique();
+ EXPECT_EQ(1U, in_flight_surfaces().size());
+ EXPECT_TRUE(displayed_frame()->texture);
+ output_surface_->PageFlipComplete();
+ CheckUnique();
+ EXPECT_EQ(0U, in_flight_surfaces().size());
+ EXPECT_EQ(1U, available_surfaces().size());
+ EXPECT_TRUE(displayed_frame()->texture);
+ output_surface_->BindFramebuffer();
+ EXPECT_EQ(2, CountBuffers());
+ CheckUnique();
+ EXPECT_TRUE(available_surfaces().empty());
+}
+
+TEST_F(BufferQueueTest, CheckTripleBuffering) {
+ // Check buffer flow through triple buffering path.
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+
+ // This bit is the same sequence tested in the doublebuffering case.
+ output_surface_->BindFramebuffer();
+ EXPECT_FALSE(displayed_frame());
+ SwapBuffers();
+ output_surface_->PageFlipComplete();
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+
+ EXPECT_EQ(2, CountBuffers());
+ CheckUnique();
+ EXPECT_EQ(1U, in_flight_surfaces().size());
+ EXPECT_TRUE(displayed_frame()->texture);
+ output_surface_->BindFramebuffer();
+ EXPECT_EQ(3, CountBuffers());
+ CheckUnique();
+ EXPECT_NE(0U, current_surface());
+ EXPECT_EQ(1U, in_flight_surfaces().size());
+ EXPECT_TRUE(displayed_frame()->texture);
+ output_surface_->PageFlipComplete();
+ EXPECT_EQ(3, CountBuffers());
+ CheckUnique();
+ EXPECT_NE(0U, current_surface());
+ EXPECT_EQ(0U, in_flight_surfaces().size());
+ EXPECT_TRUE(displayed_frame()->texture);
+ EXPECT_EQ(1U, available_surfaces().size());
+}
+
+TEST_F(BufferQueueTest, CheckEmptySwap) {
+ // Check empty swap flow, in which the damage is empty.
+ EXPECT_EQ(0, CountBuffers());
+ output_surface_->BindFramebuffer();
+ EXPECT_EQ(1, CountBuffers());
+ EXPECT_NE(0U, current_surface());
+ EXPECT_FALSE(displayed_frame());
+ SwapBuffers();
+ EXPECT_EQ(1U, in_flight_surfaces().size());
+ EXPECT_EQ(nullptr, in_flight_surfaces().front());
+ output_surface_->PageFlipComplete();
+ EXPECT_EQ(0U, in_flight_surfaces().size());
+ EXPECT_EQ(nullptr, displayed_frame());
+}
+
+TEST_F(BufferQueueTest, CheckCorrectBufferOrdering) {
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ const size_t kSwapCount = 3;
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+ }
+
+ EXPECT_EQ(kSwapCount, in_flight_surfaces().size());
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ unsigned int next_texture_id = in_flight_surfaces().front()->texture;
+ output_surface_->PageFlipComplete();
+ EXPECT_EQ(displayed_frame()->texture, next_texture_id);
+ }
+}
+
+TEST_F(BufferQueueTest, ReshapeWithInFlightSurfaces) {
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ const size_t kSwapCount = 3;
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+ }
+
+ output_surface_->Reshape(gfx::Size(10, 20), 1.0f, gfx::ColorSpace(), false);
+ EXPECT_EQ(3u, in_flight_surfaces().size());
+
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ output_surface_->PageFlipComplete();
+ EXPECT_FALSE(displayed_frame());
+ }
+
+ // The dummy surfacess left should be discarded.
+ EXPECT_EQ(0u, available_surfaces().size());
+}
+
+TEST_F(BufferQueueTest, SwapAfterReshape) {
+ DCHECK_EQ(0u, gpu_memory_buffer_manager_->set_color_space_count());
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ const size_t kSwapCount = 3;
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+ }
+ DCHECK_EQ(kSwapCount, gpu_memory_buffer_manager_->set_color_space_count());
+
+ output_surface_->Reshape(gfx::Size(10, 20), 1.0f, gfx::ColorSpace(), false);
+ DCHECK_EQ(kSwapCount, gpu_memory_buffer_manager_->set_color_space_count());
+
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+ }
+ DCHECK_EQ(2 * kSwapCount,
+ gpu_memory_buffer_manager_->set_color_space_count());
+ EXPECT_EQ(2 * kSwapCount, in_flight_surfaces().size());
+
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ output_surface_->PageFlipComplete();
+ EXPECT_FALSE(displayed_frame());
+ }
+
+ CheckUnique();
+
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ unsigned int next_texture_id = in_flight_surfaces().front()->texture;
+ output_surface_->PageFlipComplete();
+ EXPECT_EQ(displayed_frame()->texture, next_texture_id);
+ EXPECT_TRUE(displayed_frame());
+ }
+
+ DCHECK_EQ(2 * kSwapCount,
+ gpu_memory_buffer_manager_->set_color_space_count());
+ for (size_t i = 0; i < kSwapCount; ++i) {
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+ output_surface_->PageFlipComplete();
+ }
+ DCHECK_EQ(2 * kSwapCount,
+ gpu_memory_buffer_manager_->set_color_space_count());
+}
+
+TEST_F(BufferQueueMockedContextTest, RecreateBuffers) {
+ // This setup is to easily get one frame in each of:
+ // - currently bound for drawing.
+ // - in flight to GPU.
+ // - currently displayed.
+ // - free frame.
+ // This tests buffers in all states.
+ // Bind/swap pushes frames into the in flight list, then the PageFlipComplete
+ // calls pull one frame into displayed and another into the free list.
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+ output_surface_->BindFramebuffer();
+ SwapBuffers();
+ output_surface_->BindFramebuffer();
+ output_surface_->PageFlipComplete();
+ output_surface_->PageFlipComplete();
+ // We should have one buffer in each possible state right now, including one
+ // being drawn to.
+ ASSERT_EQ(1U, in_flight_surfaces().size());
+ ASSERT_EQ(1U, available_surfaces().size());
+ EXPECT_TRUE(displayed_frame());
+ EXPECT_TRUE(current_frame());
+
+ auto* current = current_frame();
+ auto* displayed = displayed_frame();
+ auto* in_flight = in_flight_surfaces().front().get();
+ auto* available = available_surfaces().front().get();
+
+ // Expect all 4 images to be destroyed, 3 of the existing textures to be
+ // copied from and 3 new images to be created.
+ EXPECT_CALL(*context_, createImageCHROMIUM(_, screen_size.width(),
+ screen_size.height(), GL_RGB))
+ .Times(3);
+ Expectation copy1 = EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, displayed->texture, _, _))
+ .Times(1);
+ Expectation copy2 = EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, current->texture, _, _))
+ .Times(1);
+ Expectation copy3 = EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(_, in_flight->texture, _, _))
+ .Times(1);
+
+ EXPECT_CALL(*context_, destroyImageCHROMIUM(displayed->image))
+ .Times(1)
+ .After(copy1);
+ EXPECT_CALL(*context_, destroyImageCHROMIUM(current->image))
+ .Times(1)
+ .After(copy2);
+ EXPECT_CALL(*context_, destroyImageCHROMIUM(in_flight->image))
+ .Times(1)
+ .After(copy3);
+ EXPECT_CALL(*context_, destroyImageCHROMIUM(available->image)).Times(1);
+ // After copying, we expect the framebuffer binding to be updated.
+ EXPECT_CALL(*context_, bindFramebuffer(_, _))
+ .After(copy1)
+ .After(copy2)
+ .After(copy3);
+ EXPECT_CALL(*context_, framebufferTexture2D(_, _, _, _, _))
+ .After(copy1)
+ .After(copy2)
+ .After(copy3);
+
+ output_surface_->RecreateBuffers();
+ testing::Mock::VerifyAndClearExpectations(context_);
+ testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
+
+ // All free buffers should be destroyed, the remaining buffers should all
+ // be replaced but still valid.
+ EXPECT_EQ(1U, in_flight_surfaces().size());
+ EXPECT_EQ(0U, available_surfaces().size());
+ EXPECT_TRUE(displayed_frame());
+ EXPECT_TRUE(current_frame());
+}
+
+TEST_F(BufferQueueTest, AllocateFails) {
+ output_surface_->Reshape(screen_size, 1.0f, gfx::ColorSpace(), false);
+
+ // Succeed in the two swaps.
+ output_surface_->BindFramebuffer();
+ EXPECT_TRUE(current_frame());
+ output_surface_->SwapBuffers(screen_rect);
+
+ // Fail the next surface allocation.
+ gpu_memory_buffer_manager_->set_allocate_succeeds(false);
+ output_surface_->BindFramebuffer();
+ EXPECT_FALSE(current_frame());
+ output_surface_->SwapBuffers(screen_rect);
+ EXPECT_FALSE(current_frame());
+
+ // Try another swap. It should copy the buffer damage from the back
+ // surface.
+ gpu_memory_buffer_manager_->set_allocate_succeeds(true);
+ output_surface_->BindFramebuffer();
+ unsigned int source_texture = in_flight_surfaces().front()->texture;
+ unsigned int target_texture = current_frame()->texture;
+ testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
+ EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(target_texture, source_texture, small_damage, _))
+ .Times(1);
+ output_surface_->SwapBuffers(small_damage);
+ testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
+
+ // Destroy the just-created buffer, and try another swap. The copy should
+ // come from the displayed surface (because both in-flight surfaces are
+ // gone now).
+ output_surface_->PageFlipComplete();
+ in_flight_surfaces().back().reset();
+ EXPECT_EQ(2u, in_flight_surfaces().size());
+ for (auto& surface : in_flight_surfaces())
+ EXPECT_FALSE(surface);
+ output_surface_->BindFramebuffer();
+ source_texture = displayed_frame()->texture;
+ EXPECT_TRUE(current_frame());
+ EXPECT_TRUE(displayed_frame());
+ target_texture = current_frame()->texture;
+ testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
+ EXPECT_CALL(*mock_output_surface_,
+ CopyBufferDamage(target_texture, source_texture, small_damage, _))
+ .Times(1);
+ output_surface_->SwapBuffers(small_damage);
+ testing::Mock::VerifyAndClearExpectations(mock_output_surface_);
+}
+
+} // namespace
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator.h b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator.h
new file mode 100644
index 00000000000..cbed76ce606
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator.h
@@ -0,0 +1,28 @@
+// 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.
+
+#ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_H_
+
+#include "base/macros.h"
+#include "cc/output/overlay_candidate_validator.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+class VIZ_SERVICE_EXPORT CompositorOverlayCandidateValidator
+ : public cc::OverlayCandidateValidator {
+ public:
+ CompositorOverlayCandidateValidator() {}
+ ~CompositorOverlayCandidateValidator() override {}
+
+ virtual void SetSoftwareMirrorMode(bool enabled) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompositorOverlayCandidateValidator);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_H_
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.cc b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.cc
new file mode 100644
index 00000000000..6c3a73a2641
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.cc
@@ -0,0 +1,68 @@
+// 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 "components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.h"
+
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "cc/output/overlay_processor.h"
+#include "cc/output/overlay_strategy_underlay.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+
+namespace viz {
+
+CompositorOverlayCandidateValidatorAndroid::
+ CompositorOverlayCandidateValidatorAndroid() {}
+
+CompositorOverlayCandidateValidatorAndroid::
+ ~CompositorOverlayCandidateValidatorAndroid() {}
+
+void CompositorOverlayCandidateValidatorAndroid::GetStrategies(
+ cc::OverlayProcessor::StrategyList* strategies) {
+ strategies->push_back(base::MakeUnique<cc::OverlayStrategyUnderlay>(this));
+}
+
+void CompositorOverlayCandidateValidatorAndroid::CheckOverlaySupport(
+ cc::OverlayCandidateList* candidates) {
+ // There should only be at most a single overlay candidate: the video quad.
+ // There's no check that the presented candidate is really a video frame for
+ // a fullscreen video. Instead it's assumed that if a quad is marked as
+ // overlayable, it's a fullscreen video quad.
+ DCHECK_LE(candidates->size(), 1u);
+
+ if (!candidates->empty()) {
+ cc::OverlayCandidate& candidate = candidates->front();
+
+ // This quad either will be promoted, or would be if it were backed by a
+ // SurfaceView. Record that it should get a promotion hint.
+ candidates->AddPromotionHint(candidate);
+
+ if (candidate.is_backed_by_surface_texture) {
+ // This quad would be promoted if it were backed by a SurfaceView. Since
+ // it isn't, we can't promote it.
+ return;
+ }
+
+ candidate.display_rect =
+ gfx::RectF(gfx::ToEnclosingRect(candidate.display_rect));
+ candidate.overlay_handled = true;
+ candidate.plane_z_order = -1;
+ }
+}
+
+bool CompositorOverlayCandidateValidatorAndroid::AllowCALayerOverlays() {
+ return false;
+}
+
+bool CompositorOverlayCandidateValidatorAndroid::AllowDCLayerOverlays() {
+ return false;
+}
+
+// Overlays will still be allowed when software mirroring is enabled, even
+// though they won't appear in the mirror.
+void CompositorOverlayCandidateValidatorAndroid::SetSoftwareMirrorMode(
+ bool enabled) {}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.h b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.h
new file mode 100644
index 00000000000..e9d3a30985e
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_android.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_ANDROID_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_ANDROID_H_
+
+#include "base/macros.h"
+#include "components/viz/service/display_embedder/compositor_overlay_candidate_validator.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+// An overlay validator for supporting fullscreen video underlays on Android.
+// Things are a bit different on Android compared with other platforms. By the
+// time a video frame is marked as overlayable it means the video decoder was
+// outputting to a Surface that we can't read back from. As a result, the
+// overlay must always succeed, or the video won't be visible. This is one of of
+// the reasons that only fullscreen is supported: we have to be sure that
+// nothing will cause the overlay to be rejected, because there's no fallback to
+// gl compositing.
+class VIZ_SERVICE_EXPORT CompositorOverlayCandidateValidatorAndroid
+ : public CompositorOverlayCandidateValidator {
+ public:
+ CompositorOverlayCandidateValidatorAndroid();
+ ~CompositorOverlayCandidateValidatorAndroid() override;
+
+ void GetStrategies(cc::OverlayProcessor::StrategyList* strategies) override;
+ void CheckOverlaySupport(cc::OverlayCandidateList* surfaces) override;
+ bool AllowCALayerOverlays() override;
+ bool AllowDCLayerOverlays() override;
+
+ void SetSoftwareMirrorMode(bool enabled) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompositorOverlayCandidateValidatorAndroid);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_ANDROID_H_
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.h b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.h
new file mode 100644
index 00000000000..aade5d1a71f
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_MAC_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_MAC_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "components/viz/service/display_embedder/compositor_overlay_candidate_validator.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+class VIZ_SERVICE_EXPORT CompositorOverlayCandidateValidatorMac
+ : public CompositorOverlayCandidateValidator {
+ public:
+ explicit CompositorOverlayCandidateValidatorMac(bool ca_layer_disabled);
+ ~CompositorOverlayCandidateValidatorMac() override;
+
+ // cc::OverlayCandidateValidator implementation.
+ void GetStrategies(cc::OverlayProcessor::StrategyList* strategies) override;
+ bool AllowCALayerOverlays() override;
+ bool AllowDCLayerOverlays() override;
+ void CheckOverlaySupport(cc::OverlayCandidateList* surfaces) override;
+
+ // CompositorOverlayCandidateValidator implementation.
+ void SetSoftwareMirrorMode(bool enabled) override;
+
+ private:
+ bool software_mirror_active_;
+ const bool ca_layer_disabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositorOverlayCandidateValidatorMac);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_MAC_H_
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.mm b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.mm
new file mode 100644
index 00000000000..fa2f5c71b61
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.mm
@@ -0,0 +1,37 @@
+// 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 "components/viz/service/display_embedder/compositor_overlay_candidate_validator_mac.h"
+
+#include <stddef.h>
+
+namespace viz {
+
+CompositorOverlayCandidateValidatorMac::CompositorOverlayCandidateValidatorMac(
+ bool ca_layer_disabled)
+ : software_mirror_active_(false), ca_layer_disabled_(ca_layer_disabled) {}
+
+CompositorOverlayCandidateValidatorMac::
+ ~CompositorOverlayCandidateValidatorMac() {}
+
+void CompositorOverlayCandidateValidatorMac::GetStrategies(
+ cc::OverlayProcessor::StrategyList* strategies) {}
+
+bool CompositorOverlayCandidateValidatorMac::AllowCALayerOverlays() {
+ return !ca_layer_disabled_ && !software_mirror_active_;
+}
+
+bool CompositorOverlayCandidateValidatorMac::AllowDCLayerOverlays() {
+ return false;
+}
+
+void CompositorOverlayCandidateValidatorMac::CheckOverlaySupport(
+ cc::OverlayCandidateList* surfaces) {}
+
+void CompositorOverlayCandidateValidatorMac::SetSoftwareMirrorMode(
+ bool enabled) {
+ software_mirror_active_ = enabled;
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.cc b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.cc
new file mode 100644
index 00000000000..84861074dad
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.cc
@@ -0,0 +1,126 @@
+// Copyright 2014 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/display_embedder/compositor_overlay_candidate_validator_ozone.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_split.h"
+#include "cc/output/overlay_strategy_fullscreen.h"
+#include "cc/output/overlay_strategy_single_on_top.h"
+#include "cc/output/overlay_strategy_underlay.h"
+#include "cc/output/overlay_strategy_underlay_cast.h"
+#include "ui/ozone/public/overlay_candidates_ozone.h"
+
+namespace viz {
+namespace {
+// Templated function used to create an OverlayProcessor::Strategy
+// of type |S|.
+template <typename S>
+std::unique_ptr<cc::OverlayProcessor::Strategy> MakeOverlayStrategy(
+ CompositorOverlayCandidateValidatorOzone* capability_checker) {
+ return base::MakeUnique<S>(capability_checker);
+}
+
+} // namespace
+
+// |overlay_candidates| is an object used to answer questions about possible
+// overlays configuarations.
+// |strategies_string| is a comma-separated string containing all the overaly
+// strategies that should be returned by GetStrategies.
+// If |strategies_string| is empty "single-on-top,underlay" will be used as
+// default.
+CompositorOverlayCandidateValidatorOzone::
+ CompositorOverlayCandidateValidatorOzone(
+ std::unique_ptr<ui::OverlayCandidatesOzone> overlay_candidates,
+ std::string strategies_string)
+ : overlay_candidates_(std::move(overlay_candidates)),
+ software_mirror_active_(false) {
+ if (!strategies_string.length())
+ strategies_string = "single-on-top,underlay";
+
+ for (const auto& strategy_name :
+ base::SplitStringPiece(strategies_string, ",", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY)) {
+ if (strategy_name == "single-fullscreen") {
+ strategies_instantiators_.push_back(
+ base::Bind(MakeOverlayStrategy<cc::OverlayStrategyFullscreen>));
+ } else if (strategy_name == "single-on-top") {
+ strategies_instantiators_.push_back(
+ base::Bind(MakeOverlayStrategy<cc::OverlayStrategySingleOnTop>));
+ } else if (strategy_name == "underlay") {
+ strategies_instantiators_.push_back(
+ base::Bind(MakeOverlayStrategy<cc::OverlayStrategyUnderlay>));
+ } else if (strategy_name == "cast") {
+ strategies_instantiators_.push_back(
+ base::Bind(MakeOverlayStrategy<cc::OverlayStrategyUnderlayCast>));
+ } else {
+ LOG(WARNING) << "Unrecognized overlay strategy " << strategy_name;
+ }
+ }
+}
+
+CompositorOverlayCandidateValidatorOzone::
+ ~CompositorOverlayCandidateValidatorOzone() {}
+
+void CompositorOverlayCandidateValidatorOzone::GetStrategies(
+ cc::OverlayProcessor::StrategyList* strategies) {
+ for (auto& instantiator : strategies_instantiators_)
+ strategies->push_back(instantiator.Run(this));
+}
+
+bool CompositorOverlayCandidateValidatorOzone::AllowCALayerOverlays() {
+ return false;
+}
+
+bool CompositorOverlayCandidateValidatorOzone::AllowDCLayerOverlays() {
+ return false;
+}
+
+void CompositorOverlayCandidateValidatorOzone::CheckOverlaySupport(
+ cc::OverlayCandidateList* surfaces) {
+ // SW mirroring copies out of the framebuffer, so we can't remove any
+ // quads for overlaying, otherwise the output is incorrect.
+ if (software_mirror_active_) {
+ for (size_t i = 0; i < surfaces->size(); i++) {
+ surfaces->at(i).overlay_handled = false;
+ }
+ return;
+ }
+
+ DCHECK_GE(2U, surfaces->size());
+ ui::OverlayCandidatesOzone::OverlaySurfaceCandidateList ozone_surface_list;
+ ozone_surface_list.resize(surfaces->size());
+
+ for (size_t i = 0; i < surfaces->size(); i++) {
+ ozone_surface_list.at(i).transform = surfaces->at(i).transform;
+ ozone_surface_list.at(i).format = surfaces->at(i).format;
+ ozone_surface_list.at(i).display_rect = surfaces->at(i).display_rect;
+ ozone_surface_list.at(i).crop_rect = surfaces->at(i).uv_rect;
+ ozone_surface_list.at(i).clip_rect = surfaces->at(i).clip_rect;
+ ozone_surface_list.at(i).is_clipped = surfaces->at(i).is_clipped;
+ ozone_surface_list.at(i).plane_z_order = surfaces->at(i).plane_z_order;
+ ozone_surface_list.at(i).buffer_size =
+ surfaces->at(i).resource_size_in_pixels;
+ }
+
+ overlay_candidates_->CheckOverlaySupport(&ozone_surface_list);
+ DCHECK_EQ(surfaces->size(), ozone_surface_list.size());
+
+ for (size_t i = 0; i < surfaces->size(); i++) {
+ surfaces->at(i).overlay_handled = ozone_surface_list.at(i).overlay_handled;
+ surfaces->at(i).display_rect = ozone_surface_list.at(i).display_rect;
+ }
+}
+
+void CompositorOverlayCandidateValidatorOzone::SetSoftwareMirrorMode(
+ bool enabled) {
+ software_mirror_active_ = enabled;
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.h b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.h
new file mode 100644
index 00000000000..4ba9429d14a
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_ozone.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_OZONE_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_OZONE_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "components/viz/service/display_embedder/compositor_overlay_candidate_validator.h"
+#include "components/viz/service/viz_service_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace ui {
+class OverlayCandidatesOzone;
+}
+
+namespace viz {
+
+class VIZ_SERVICE_EXPORT CompositorOverlayCandidateValidatorOzone
+ : public CompositorOverlayCandidateValidator {
+ public:
+ CompositorOverlayCandidateValidatorOzone(
+ std::unique_ptr<ui::OverlayCandidatesOzone> overlay_candidates,
+ std::string strategies_string);
+ ~CompositorOverlayCandidateValidatorOzone() override;
+
+ // cc::OverlayCandidateValidator implementation.
+ void GetStrategies(cc::OverlayProcessor::StrategyList* strategies) override;
+ bool AllowCALayerOverlays() override;
+ bool AllowDCLayerOverlays() override;
+ void CheckOverlaySupport(cc::OverlayCandidateList* surfaces) override;
+
+ // CompositorOverlayCandidateValidator implementation.
+ void SetSoftwareMirrorMode(bool enabled) override;
+
+ private:
+ std::unique_ptr<ui::OverlayCandidatesOzone> overlay_candidates_;
+ // Callback declaration to allocate a new OverlayProcessor::Strategy.
+ using StrategyInstantiator =
+ base::Callback<std::unique_ptr<cc::OverlayProcessor::Strategy>(
+ CompositorOverlayCandidateValidatorOzone*)>;
+ // List callbacks used to instantiate OverlayProcessor::Strategy
+ // as defined by |strategies_string| paramter in the constructor.
+ std::vector<StrategyInstantiator> strategies_instantiators_;
+ bool software_mirror_active_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositorOverlayCandidateValidatorOzone);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_OZONE_H_
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_win.cc b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_win.cc
new file mode 100644
index 00000000000..bb22c575671
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_win.cc
@@ -0,0 +1,39 @@
+// 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/display_embedder/compositor_overlay_candidate_validator_win.h"
+
+#include "cc/output/overlay_processor.h"
+
+namespace viz {
+
+CompositorOverlayCandidateValidatorWin::
+ CompositorOverlayCandidateValidatorWin() {}
+
+CompositorOverlayCandidateValidatorWin::
+ ~CompositorOverlayCandidateValidatorWin() {}
+
+void CompositorOverlayCandidateValidatorWin::GetStrategies(
+ cc::OverlayProcessor::StrategyList* strategies) {}
+
+void CompositorOverlayCandidateValidatorWin::CheckOverlaySupport(
+ cc::OverlayCandidateList* candidates) {
+ NOTIMPLEMENTED();
+}
+
+bool CompositorOverlayCandidateValidatorWin::AllowCALayerOverlays() {
+ return false;
+}
+
+bool CompositorOverlayCandidateValidatorWin::AllowDCLayerOverlays() {
+ return true;
+}
+
+void CompositorOverlayCandidateValidatorWin::SetSoftwareMirrorMode(
+ bool enabled) {
+ // Software mirroring isn't supported on Windows.
+ NOTIMPLEMENTED();
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_win.h b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_win.h
new file mode 100644
index 00000000000..c5a20c6d5c5
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/compositor_overlay_candidate_validator_win.h
@@ -0,0 +1,35 @@
+// 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_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_WIN_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_WIN_H_
+
+#include "base/macros.h"
+#include "components/viz/service/display_embedder/compositor_overlay_candidate_validator.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+// This is a simple overlay candidate validator that promotes everything
+// possible to an overlay.
+class VIZ_SERVICE_EXPORT CompositorOverlayCandidateValidatorWin
+ : public CompositorOverlayCandidateValidator {
+ public:
+ CompositorOverlayCandidateValidatorWin();
+ ~CompositorOverlayCandidateValidatorWin() override;
+
+ void GetStrategies(cc::OverlayProcessor::StrategyList* strategies) override;
+ void CheckOverlaySupport(cc::OverlayCandidateList* surfaces) override;
+ bool AllowCALayerOverlays() override;
+ bool AllowDCLayerOverlays() override;
+
+ void SetSoftwareMirrorMode(bool enabled) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompositorOverlayCandidateValidatorWin);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_COMPOSITOR_OVERLAY_CANDIDATE_VALIDATOR_WIN_H_
diff --git a/chromium/components/viz/service/display_embedder/display_output_surface.cc b/chromium/components/viz/service/display_embedder/display_output_surface.cc
new file mode 100644
index 00000000000..b293f8b82ce
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/display_output_surface.cc
@@ -0,0 +1,152 @@
+// Copyright 2014 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/display_embedder/display_output_surface.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "cc/output/output_surface_client.h"
+#include "cc/output/output_surface_frame.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "components/viz/common/gpu/context_provider.h"
+#include "gpu/command_buffer/client/context_support.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+
+namespace viz {
+
+DisplayOutputSurface::DisplayOutputSurface(
+ scoped_refptr<InProcessContextProvider> context_provider,
+ cc::SyntheticBeginFrameSource* synthetic_begin_frame_source)
+ : cc::OutputSurface(context_provider),
+ synthetic_begin_frame_source_(synthetic_begin_frame_source),
+ weak_ptr_factory_(this) {
+ capabilities_.flipped_output_surface =
+ context_provider->ContextCapabilities().flips_vertically;
+ capabilities_.supports_stencil =
+ context_provider->ContextCapabilities().num_stencil_bits > 0;
+ context_provider->SetSwapBuffersCompletionCallback(
+ base::Bind(&DisplayOutputSurface::OnGpuSwapBuffersCompleted,
+ weak_ptr_factory_.GetWeakPtr()));
+ context_provider->SetUpdateVSyncParametersCallback(
+ base::Bind(&DisplayOutputSurface::OnVSyncParametersUpdated,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+DisplayOutputSurface::~DisplayOutputSurface() {}
+
+void DisplayOutputSurface::BindToClient(cc::OutputSurfaceClient* client) {
+ DCHECK(client);
+ DCHECK(!client_);
+ client_ = client;
+}
+
+void DisplayOutputSurface::EnsureBackbuffer() {}
+
+void DisplayOutputSurface::DiscardBackbuffer() {
+ context_provider()->ContextGL()->DiscardBackbufferCHROMIUM();
+}
+
+void DisplayOutputSurface::BindFramebuffer() {
+ context_provider()->ContextGL()->BindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+void DisplayOutputSurface::SetDrawRectangle(const gfx::Rect& rect) {
+ if (set_draw_rectangle_for_frame_)
+ return;
+ DCHECK(gfx::Rect(size_).Contains(rect));
+ DCHECK(has_set_draw_rectangle_since_last_resize_ ||
+ (gfx::Rect(size_) == rect));
+ set_draw_rectangle_for_frame_ = true;
+ has_set_draw_rectangle_since_last_resize_ = true;
+ context_provider()->ContextGL()->SetDrawRectangleCHROMIUM(
+ rect.x(), rect.y(), rect.width(), rect.height());
+}
+
+void DisplayOutputSurface::Reshape(const gfx::Size& size,
+ float device_scale_factor,
+ const gfx::ColorSpace& color_space,
+ bool has_alpha,
+ bool use_stencil) {
+ size_ = size;
+ has_set_draw_rectangle_since_last_resize_ = false;
+ context_provider()->ContextGL()->ResizeCHROMIUM(
+ size.width(), size.height(), device_scale_factor, has_alpha);
+}
+
+void DisplayOutputSurface::SwapBuffers(cc::OutputSurfaceFrame frame) {
+ DCHECK(context_provider_);
+
+ if (frame.latency_info.size() > 0)
+ context_provider_->ContextSupport()->AddLatencyInfo(frame.latency_info);
+
+ set_draw_rectangle_for_frame_ = false;
+ if (frame.sub_buffer_rect) {
+ context_provider_->ContextSupport()->PartialSwapBuffers(
+ *frame.sub_buffer_rect);
+ } else {
+ context_provider_->ContextSupport()->Swap();
+ }
+}
+
+uint32_t DisplayOutputSurface::GetFramebufferCopyTextureFormat() {
+ // TODO(danakj): What attributes are used for the default framebuffer here?
+ // Can it have alpha? InProcessContextProvider doesn't take any
+ // attributes.
+ return GL_RGB;
+}
+
+cc::OverlayCandidateValidator*
+DisplayOutputSurface::GetOverlayCandidateValidator() const {
+ return nullptr;
+}
+
+bool DisplayOutputSurface::IsDisplayedAsOverlayPlane() const {
+ return false;
+}
+
+unsigned DisplayOutputSurface::GetOverlayTextureId() const {
+ return 0;
+}
+
+gfx::BufferFormat DisplayOutputSurface::GetOverlayBufferFormat() const {
+ return gfx::BufferFormat::RGBX_8888;
+}
+
+bool DisplayOutputSurface::SurfaceIsSuspendForRecycle() const {
+ return false;
+}
+
+bool DisplayOutputSurface::HasExternalStencilTest() const {
+ return false;
+}
+
+void DisplayOutputSurface::ApplyExternalStencil() {}
+
+void DisplayOutputSurface::DidReceiveSwapBuffersAck(gfx::SwapResult result) {
+ client_->DidReceiveSwapBuffersAck();
+}
+
+void DisplayOutputSurface::OnGpuSwapBuffersCompleted(
+ const std::vector<ui::LatencyInfo>& latency_info,
+ gfx::SwapResult result,
+ const gpu::GpuProcessHostedCALayerTreeParamsMac* params_mac) {
+ for (const auto& latency : latency_info) {
+ if (latency.latency_components().size() > 0)
+ latency_tracker_.OnGpuSwapBuffersCompleted(latency);
+ }
+ DidReceiveSwapBuffersAck(result);
+}
+
+void DisplayOutputSurface::OnVSyncParametersUpdated(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ // TODO(brianderson): We should not be receiving 0 intervals.
+ synthetic_begin_frame_source_->OnUpdateVSyncParameters(
+ timebase,
+ interval.is_zero() ? cc::BeginFrameArgs::DefaultInterval() : interval);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/display_output_surface.h b/chromium/components/viz/service/display_embedder/display_output_surface.h
new file mode 100644
index 00000000000..7e59c6a160b
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/display_output_surface.h
@@ -0,0 +1,79 @@
+// Copyright 2014 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_DISPLAY_EMBEDDER_DISPLAY_OUTPUT_SURFACE_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_DISPLAY_OUTPUT_SURFACE_H_
+
+#include <memory>
+
+#include "cc/output/output_surface.h"
+#include "components/viz/common/gpu/in_process_context_provider.h"
+#include "ui/latency/latency_tracker.h"
+
+namespace cc {
+class SyntheticBeginFrameSource;
+}
+
+namespace viz {
+
+// An OutputSurface implementation that directly draws and
+// swaps to an actual GL surface.
+class DisplayOutputSurface : public cc::OutputSurface {
+ public:
+ DisplayOutputSurface(
+ scoped_refptr<InProcessContextProvider> context_provider,
+ cc::SyntheticBeginFrameSource* synthetic_begin_frame_source);
+ ~DisplayOutputSurface() override;
+
+ // cc::OutputSurface implementation
+ void BindToClient(cc::OutputSurfaceClient* client) override;
+ void EnsureBackbuffer() override;
+ void DiscardBackbuffer() override;
+ void BindFramebuffer() override;
+ void SetDrawRectangle(const gfx::Rect& draw_rectangle) override;
+ void Reshape(const gfx::Size& size,
+ float device_scale_factor,
+ const gfx::ColorSpace& color_space,
+ bool has_alpha,
+ bool use_stencil) override;
+ void SwapBuffers(cc::OutputSurfaceFrame frame) override;
+ uint32_t GetFramebufferCopyTextureFormat() override;
+ cc::OverlayCandidateValidator* GetOverlayCandidateValidator() const override;
+ bool IsDisplayedAsOverlayPlane() const override;
+ unsigned GetOverlayTextureId() const override;
+ gfx::BufferFormat GetOverlayBufferFormat() const override;
+ bool SurfaceIsSuspendForRecycle() const override;
+ bool HasExternalStencilTest() const override;
+ void ApplyExternalStencil() override;
+
+ protected:
+ cc::OutputSurfaceClient* client() const { return client_; }
+
+ // Called when a swap completion is signaled from ImageTransportSurface.
+ virtual void DidReceiveSwapBuffersAck(gfx::SwapResult result);
+
+ private:
+ // Called when a swap completion is signaled from ImageTransportSurface.
+ void OnGpuSwapBuffersCompleted(
+ const std::vector<ui::LatencyInfo>& latency_info,
+ gfx::SwapResult result,
+ const gpu::GpuProcessHostedCALayerTreeParamsMac* params_mac);
+ void OnVSyncParametersUpdated(base::TimeTicks timebase,
+ base::TimeDelta interval);
+
+ cc::OutputSurfaceClient* client_ = nullptr;
+ cc::SyntheticBeginFrameSource* const synthetic_begin_frame_source_;
+ ui::LatencyTracker latency_tracker_;
+
+ bool set_draw_rectangle_for_frame_ = false;
+ // True if the draw rectangle has been set at all since the last resize.
+ bool has_set_draw_rectangle_since_last_resize_ = false;
+ gfx::Size size_;
+
+ base::WeakPtrFactory<DisplayOutputSurface> weak_ptr_factory_;
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_DISPLAY_OUTPUT_SURFACE_H_
diff --git a/chromium/components/viz/service/display_embedder/display_output_surface_ozone.cc b/chromium/components/viz/service/display_embedder/display_output_surface_ozone.cc
new file mode 100644
index 00000000000..42df4118c21
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/display_output_surface_ozone.cc
@@ -0,0 +1,127 @@
+// Copyright 2016 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/display_embedder/display_output_surface_ozone.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "cc/output/output_surface_client.h"
+#include "cc/output/output_surface_frame.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "components/viz/common/gpu/context_provider.h"
+#include "components/viz/service/display_embedder/buffer_queue.h"
+#include "gpu/command_buffer/client/context_support.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "ui/display/types/display_snapshot.h"
+
+namespace viz {
+
+DisplayOutputSurfaceOzone::DisplayOutputSurfaceOzone(
+ scoped_refptr<InProcessContextProvider> context_provider,
+ gfx::AcceleratedWidget widget,
+ cc::SyntheticBeginFrameSource* synthetic_begin_frame_source,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ uint32_t target,
+ uint32_t internalformat)
+ : DisplayOutputSurface(context_provider, synthetic_begin_frame_source),
+ gl_helper_(context_provider->ContextGL(),
+ context_provider->ContextSupport()) {
+ capabilities_.uses_default_gl_framebuffer = false;
+ capabilities_.flipped_output_surface = true;
+ // Set |max_frames_pending| to 2 for surfaceless, which aligns scheduling
+ // more closely with the previous surfaced behavior.
+ // With a surface, swap buffer ack used to return early, before actually
+ // presenting the back buffer, enabling the browser compositor to run ahead.
+ // Surfaceless implementation acks at the time of actual buffer swap, which
+ // shifts the start of the new frame forward relative to the old
+ // implementation.
+ capabilities_.max_frames_pending = 2;
+
+ buffer_queue_.reset(
+ new BufferQueue(context_provider->ContextGL(), target, internalformat,
+ display::DisplaySnapshot::PrimaryFormat(), &gl_helper_,
+ gpu_memory_buffer_manager, widget));
+ buffer_queue_->Initialize();
+}
+
+DisplayOutputSurfaceOzone::~DisplayOutputSurfaceOzone() {
+ // TODO(rjkroege): Support cleanup.
+}
+
+void DisplayOutputSurfaceOzone::BindFramebuffer() {
+ DCHECK(buffer_queue_);
+ buffer_queue_->BindFramebuffer();
+}
+
+// We call this on every frame that a value changes, but changing the size once
+// we've allocated backing NativePixmapOzone instances will cause a DCHECK
+// because Chrome never Reshape(s) after the first one from (0,0). NB: this
+// implies that screen size changes need to be plumbed differently. In
+// particular, we must create the native window in the size that the hardware
+// reports.
+void DisplayOutputSurfaceOzone::Reshape(const gfx::Size& size,
+ float device_scale_factor,
+ const gfx::ColorSpace& color_space,
+ bool has_alpha,
+ bool use_stencil) {
+ reshape_size_ = size;
+ DisplayOutputSurface::Reshape(size, device_scale_factor, color_space,
+ has_alpha, use_stencil);
+ buffer_queue_->Reshape(size, device_scale_factor, color_space, use_stencil);
+}
+
+void DisplayOutputSurfaceOzone::SwapBuffers(cc::OutputSurfaceFrame frame) {
+ DCHECK(buffer_queue_);
+
+ // TODO(rjkroege): What if swap happens again before DidReceiveSwapBuffersAck
+ // then it would see the wrong size?
+ DCHECK(reshape_size_ == frame.size);
+ swap_size_ = reshape_size_;
+
+ gfx::Rect damage_rect =
+ frame.sub_buffer_rect ? *frame.sub_buffer_rect : gfx::Rect(swap_size_);
+ buffer_queue_->SwapBuffers(damage_rect);
+
+ DisplayOutputSurface::SwapBuffers(std::move(frame));
+}
+
+uint32_t DisplayOutputSurfaceOzone::GetFramebufferCopyTextureFormat() {
+ return buffer_queue_->internal_format();
+}
+
+bool DisplayOutputSurfaceOzone::IsDisplayedAsOverlayPlane() const {
+ // TODO(rjkroege): implement remaining overlay functionality.
+ return true;
+}
+
+unsigned DisplayOutputSurfaceOzone::GetOverlayTextureId() const {
+ return buffer_queue_->GetCurrentTextureId();
+}
+
+gfx::BufferFormat DisplayOutputSurfaceOzone::GetOverlayBufferFormat() const {
+ DCHECK(buffer_queue_);
+ return buffer_queue_->buffer_format();
+}
+
+void DisplayOutputSurfaceOzone::DidReceiveSwapBuffersAck(
+ gfx::SwapResult result) {
+ bool force_swap = false;
+ if (result == gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS) {
+ // Even through the swap failed, this is a fixable error so we can pretend
+ // it succeeded to the rest of the system.
+ result = gfx::SwapResult::SWAP_ACK;
+ buffer_queue_->RecreateBuffers();
+ force_swap = true;
+ }
+
+ buffer_queue_->PageFlipComplete();
+ client()->DidReceiveSwapBuffersAck();
+
+ if (force_swap)
+ client()->SetNeedsRedrawRect(gfx::Rect(swap_size_));
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/display_output_surface_ozone.h b/chromium/components/viz/service/display_embedder/display_output_surface_ozone.h
new file mode 100644
index 00000000000..f49180cf708
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/display_output_surface_ozone.h
@@ -0,0 +1,79 @@
+// Copyright 2016 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_DISPLAY_EMBEDDER_DISPLAY_OUTPUT_SURFACE_OZONE_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_DISPLAY_OUTPUT_SURFACE_OZONE_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "cc/output/output_surface.h"
+#include "components/viz/common/gl_helper.h"
+#include "components/viz/common/gpu/context_provider.h"
+#include "components/viz/common/gpu/in_process_context_provider.h"
+#include "components/viz/service/display_embedder/display_output_surface.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/swap_result.h"
+#include "ui/gl/gl_surface.h"
+
+namespace cc {
+class SyntheticBeginFrameSource;
+}
+
+namespace gpu {
+class GpuMemoryBufferManager;
+}
+
+namespace viz {
+
+class BufferQueue;
+
+// An OutputSurface implementation that directly draws and swap to a GL
+// "surfaceless" surface (aka one backed by a buffer managed explicitly in
+// mus/ozone. This class is adapted from
+// GpuSurfacelessBrowserCompositorOutputSurface.
+class DisplayOutputSurfaceOzone : public DisplayOutputSurface {
+ public:
+ DisplayOutputSurfaceOzone(
+ scoped_refptr<InProcessContextProvider> context_provider,
+ gfx::AcceleratedWidget widget,
+ cc::SyntheticBeginFrameSource* synthetic_begin_frame_source,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ uint32_t target,
+ uint32_t internalformat);
+
+ ~DisplayOutputSurfaceOzone() override;
+
+ // TODO(rjkroege): Implement the equivalent of Reflector.
+
+ private:
+ // cc::OutputSurface implementation.
+ void BindFramebuffer() override;
+ void Reshape(const gfx::Size& size,
+ float device_scale_factor,
+ const gfx::ColorSpace& color_space,
+ bool has_alpha,
+ bool use_stencil) override;
+ void SwapBuffers(cc::OutputSurfaceFrame frame) override;
+ uint32_t GetFramebufferCopyTextureFormat() override;
+ bool IsDisplayedAsOverlayPlane() const override;
+ unsigned GetOverlayTextureId() const override;
+ gfx::BufferFormat GetOverlayBufferFormat() const override;
+
+ // DisplayOutputSurface:
+ void DidReceiveSwapBuffersAck(gfx::SwapResult result) override;
+
+ GLHelper gl_helper_;
+ std::unique_ptr<BufferQueue> buffer_queue_;
+
+ gfx::Size reshape_size_;
+ gfx::Size swap_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayOutputSurfaceOzone);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_DISPLAY_OUTPUT_SURFACE_OZONE_H_
diff --git a/chromium/components/viz/service/display_embedder/display_provider.h b/chromium/components/viz/service/display_embedder/display_provider.h
new file mode 100644
index 00000000000..a6cba01d045
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/display_provider.h
@@ -0,0 +1,35 @@
+// 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_DISPLAY_EMBEDDER_DISPLAY_PROVIDER_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_DISPLAY_PROVIDER_H_
+
+#include <memory>
+
+#include "gpu/ipc/common/surface_handle.h"
+
+namespace cc {
+class BeginFrameSource;
+class FrameSinkId;
+} // namespace cc
+
+namespace viz {
+class Display;
+
+// Handles creating Display and related classes for FrameSinkManagerImpl.
+class DisplayProvider {
+ public:
+ virtual ~DisplayProvider() {}
+
+ // Creates a new Display for |surface_handle| with |frame_sink_id|. Will
+ // also create cc::BeginFrameSource and return it in |begin_frame_source|.
+ virtual std::unique_ptr<Display> CreateDisplay(
+ const FrameSinkId& frame_sink_id,
+ gpu::SurfaceHandle surface_handle,
+ std::unique_ptr<cc::BeginFrameSource>* begin_frame_source) = 0;
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_DISPLAY_PROVIDER_H_
diff --git a/chromium/components/viz/service/display_embedder/gpu_display_provider.cc b/chromium/components/viz/service/display_embedder/gpu_display_provider.cc
new file mode 100644
index 00000000000..57f6f16e8bb
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/gpu_display_provider.cc
@@ -0,0 +1,109 @@
+// 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/display_embedder/gpu_display_provider.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/memory/ptr_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "cc/base/switches.h"
+#include "cc/output/texture_mailbox_deleter.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "components/viz/common/gpu/in_process_context_provider.h"
+#include "components/viz/service/display/display.h"
+#include "components/viz/service/display/display_scheduler.h"
+#include "components/viz/service/display_embedder/display_output_surface.h"
+#include "components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.h"
+#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
+#include "gpu/command_buffer/client/shared_memory_limits.h"
+#include "gpu/command_buffer/service/image_factory.h"
+#include "gpu/ipc/service/gpu_channel_manager.h"
+#include "gpu/ipc/service/gpu_memory_buffer_factory.h"
+
+#if defined(USE_OZONE)
+#include "components/viz/service/display_embedder/display_output_surface_ozone.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#endif
+
+namespace {
+
+gpu::ImageFactory* GetImageFactory(gpu::GpuChannelManager* channel_manager) {
+ auto* buffer_factory = channel_manager->gpu_memory_buffer_factory();
+ return buffer_factory ? buffer_factory->AsImageFactory() : nullptr;
+}
+
+} // namespace
+
+namespace viz {
+
+GpuDisplayProvider::GpuDisplayProvider(
+ scoped_refptr<gpu::InProcessCommandBuffer::Service> gpu_service,
+ gpu::GpuChannelManager* gpu_channel_manager)
+ : gpu_service_(std::move(gpu_service)),
+ gpu_memory_buffer_manager_(
+ base::MakeUnique<InProcessGpuMemoryBufferManager>(
+ gpu_channel_manager)),
+ image_factory_(GetImageFactory(gpu_channel_manager)),
+ task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+
+GpuDisplayProvider::~GpuDisplayProvider() = default;
+
+std::unique_ptr<Display> GpuDisplayProvider::CreateDisplay(
+ const FrameSinkId& frame_sink_id,
+ gpu::SurfaceHandle surface_handle,
+ std::unique_ptr<cc::BeginFrameSource>* begin_frame_source) {
+ auto synthetic_begin_frame_source =
+ base::MakeUnique<cc::DelayBasedBeginFrameSource>(
+ base::MakeUnique<cc::DelayBasedTimeSource>(task_runner_.get()));
+
+ scoped_refptr<InProcessContextProvider> context_provider =
+ new InProcessContextProvider(gpu_service_, surface_handle,
+ gpu_memory_buffer_manager_.get(),
+ image_factory_, gpu::SharedMemoryLimits(),
+ nullptr /* shared_context */);
+
+ // TODO(rjkroege): If there is something better to do than CHECK, add it.
+ CHECK(context_provider->BindToCurrentThread());
+
+ std::unique_ptr<cc::OutputSurface> display_output_surface;
+ if (context_provider->ContextCapabilities().surfaceless) {
+#if defined(USE_OZONE)
+ display_output_surface = base::MakeUnique<DisplayOutputSurfaceOzone>(
+ std::move(context_provider), surface_handle,
+ synthetic_begin_frame_source.get(), gpu_memory_buffer_manager_.get(),
+ GL_TEXTURE_2D, GL_RGB);
+#else
+ NOTREACHED();
+#endif
+ } else {
+ display_output_surface = base::MakeUnique<DisplayOutputSurface>(
+ std::move(context_provider), synthetic_begin_frame_source.get());
+ }
+
+ int max_frames_pending =
+ display_output_surface->capabilities().max_frames_pending;
+ DCHECK_GT(max_frames_pending, 0);
+
+ auto scheduler = base::MakeUnique<DisplayScheduler>(
+ synthetic_begin_frame_source.get(), task_runner_.get(),
+ max_frames_pending);
+
+ RendererSettings settings;
+ settings.show_overdraw_feedback =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ cc::switches::kShowOverdrawFeedback);
+
+ // The ownership of the BeginFrameSource is transfered to the caller.
+ *begin_frame_source = std::move(synthetic_begin_frame_source);
+
+ return base::MakeUnique<Display>(
+ ServerSharedBitmapManager::current(), gpu_memory_buffer_manager_.get(),
+ settings, frame_sink_id, std::move(display_output_surface),
+ std::move(scheduler),
+ base::MakeUnique<cc::TextureMailboxDeleter>(task_runner_.get()));
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/gpu_display_provider.h b/chromium/components/viz/service/display_embedder/gpu_display_provider.h
new file mode 100644
index 00000000000..f9c320ccb18
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/gpu_display_provider.h
@@ -0,0 +1,55 @@
+// 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_DISPLAY_EMBEDDER_GPU_DISPLAY_PROVIDER_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_GPU_DISPLAY_PROVIDER_H_
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/service/display_embedder/display_provider.h"
+#include "components/viz/service/viz_service_export.h"
+#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
+#include "gpu/ipc/common/surface_handle.h"
+#include "gpu/ipc/in_process_command_buffer.h"
+
+namespace gpu {
+class GpuChannelManager;
+class ImageFactory;
+} // namespace gpu
+
+namespace viz {
+class Display;
+
+// In-process implementation of DisplayProvider.
+class VIZ_SERVICE_EXPORT GpuDisplayProvider
+ : public NON_EXPORTED_BASE(DisplayProvider) {
+ public:
+ GpuDisplayProvider(
+ scoped_refptr<gpu::InProcessCommandBuffer::Service> gpu_service,
+ gpu::GpuChannelManager* gpu_channel_manager);
+ ~GpuDisplayProvider() override;
+
+ // DisplayProvider:
+ std::unique_ptr<Display> CreateDisplay(
+ const FrameSinkId& frame_sink_id,
+ gpu::SurfaceHandle surface_handle,
+ std::unique_ptr<cc::BeginFrameSource>* begin_frame_source) override;
+
+ private:
+ scoped_refptr<gpu::InProcessCommandBuffer::Service> gpu_service_;
+ std::unique_ptr<gpu::GpuMemoryBufferManager> gpu_memory_buffer_manager_;
+ gpu::ImageFactory* image_factory_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuDisplayProvider);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_GPU_DISPLAY_PROVIDER_H_
diff --git a/chromium/components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.cc b/chromium/components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.cc
new file mode 100644
index 00000000000..fd4d44d5266
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.cc
@@ -0,0 +1,51 @@
+// 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/display_embedder/in_process_gpu_memory_buffer_manager.h"
+
+#include "gpu/ipc/client/gpu_memory_buffer_impl.h"
+#include "gpu/ipc/service/gpu_channel_manager.h"
+#include "gpu/ipc/service/gpu_memory_buffer_factory.h"
+
+namespace viz {
+
+InProcessGpuMemoryBufferManager::InProcessGpuMemoryBufferManager(
+ gpu::GpuChannelManager* channel_manager)
+ : client_id_(1), channel_manager_(channel_manager), weak_factory_(this) {
+ weak_ptr_ = weak_factory_.GetWeakPtr();
+}
+
+InProcessGpuMemoryBufferManager::~InProcessGpuMemoryBufferManager() {}
+
+std::unique_ptr<gfx::GpuMemoryBuffer>
+InProcessGpuMemoryBufferManager::CreateGpuMemoryBuffer(
+ const gfx::Size& size,
+ gfx::BufferFormat format,
+ gfx::BufferUsage usage,
+ gpu::SurfaceHandle surface_handle) {
+ gfx::GpuMemoryBufferId id(next_gpu_memory_id_++);
+ gfx::GpuMemoryBufferHandle buffer_handle =
+ channel_manager_->gpu_memory_buffer_factory()->CreateGpuMemoryBuffer(
+ id, size, format, usage, client_id_, surface_handle);
+ return gpu::GpuMemoryBufferImpl::CreateFromHandle(
+ buffer_handle, size, format, usage,
+ base::Bind(&InProcessGpuMemoryBufferManager::DestroyGpuMemoryBuffer,
+ weak_ptr_, id, client_id_));
+}
+
+void InProcessGpuMemoryBufferManager::SetDestructionSyncToken(
+ gfx::GpuMemoryBuffer* buffer,
+ const gpu::SyncToken& sync_token) {
+ static_cast<gpu::GpuMemoryBufferImpl*>(buffer)->set_destruction_sync_token(
+ sync_token);
+}
+
+void InProcessGpuMemoryBufferManager::DestroyGpuMemoryBuffer(
+ gfx::GpuMemoryBufferId id,
+ int client_id,
+ const gpu::SyncToken& sync_token) {
+ channel_manager_->DestroyGpuMemoryBuffer(id, client_id, sync_token);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.h b/chromium/components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.h
new file mode 100644
index 00000000000..4074531d145
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.h
@@ -0,0 +1,47 @@
+// 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_DISPLAY_EMBEDDER_IN_PROCESS_GPU_MEMORY_BUFFER_MANAGER_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_IN_PROCESS_GPU_MEMORY_BUFFER_MANAGER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
+
+namespace gpu {
+class GpuChannelManager;
+}
+
+namespace viz {
+
+class InProcessGpuMemoryBufferManager : public gpu::GpuMemoryBufferManager {
+ public:
+ explicit InProcessGpuMemoryBufferManager(
+ gpu::GpuChannelManager* channel_manager);
+
+ ~InProcessGpuMemoryBufferManager() override;
+
+ // gpu::GpuMemoryBufferManager:
+ std::unique_ptr<gfx::GpuMemoryBuffer> CreateGpuMemoryBuffer(
+ const gfx::Size& size,
+ gfx::BufferFormat format,
+ gfx::BufferUsage usage,
+ gpu::SurfaceHandle surface_handle) override;
+ void SetDestructionSyncToken(gfx::GpuMemoryBuffer* buffer,
+ const gpu::SyncToken& sync_token) override;
+
+ private:
+ void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id,
+ int client_id,
+ const gpu::SyncToken& sync_token);
+ const int client_id_;
+ int next_gpu_memory_id_ = 1;
+ gpu::GpuChannelManager* channel_manager_;
+ base::WeakPtr<InProcessGpuMemoryBufferManager> weak_ptr_;
+ base::WeakPtrFactory<InProcessGpuMemoryBufferManager> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(InProcessGpuMemoryBufferManager);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_IN_PROCESS_GPU_MEMORY_BUFFER_MANAGER_H_
diff --git a/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager.cc b/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager.cc
new file mode 100644
index 00000000000..07a45c9b72b
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager.cc
@@ -0,0 +1,193 @@
+// Copyright 2013 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/display_embedder/server_shared_bitmap_manager.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace viz {
+
+class BitmapData : public base::RefCountedThreadSafe<BitmapData> {
+ public:
+ explicit BitmapData(size_t buffer_size) : buffer_size(buffer_size) {}
+ std::unique_ptr<base::SharedMemory> memory;
+ std::unique_ptr<uint8_t[]> pixels;
+ size_t buffer_size;
+
+ private:
+ friend class base::RefCountedThreadSafe<BitmapData>;
+ ~BitmapData() {}
+ DISALLOW_COPY_AND_ASSIGN(BitmapData);
+};
+
+namespace {
+
+class ServerSharedBitmap : public SharedBitmap {
+ public:
+ ServerSharedBitmap(uint8_t* pixels,
+ scoped_refptr<BitmapData> bitmap_data,
+ const SharedBitmapId& id,
+ ServerSharedBitmapManager* manager)
+ : SharedBitmap(pixels, id, 0 /* sequence_number */),
+ bitmap_data_(bitmap_data),
+ manager_(manager) {}
+
+ ~ServerSharedBitmap() override {
+ if (manager_)
+ manager_->FreeSharedMemoryFromMap(id());
+ }
+
+ // SharedBitmap:
+ base::SharedMemoryHandle GetSharedMemoryHandle() const override {
+ if (!bitmap_data_->memory)
+ return base::SharedMemoryHandle();
+ return bitmap_data_->memory->handle();
+ }
+
+ private:
+ scoped_refptr<BitmapData> bitmap_data_;
+ ServerSharedBitmapManager* manager_;
+};
+
+} // namespace
+
+base::LazyInstance<ServerSharedBitmapManager>::DestructorAtExit
+ g_shared_memory_manager = LAZY_INSTANCE_INITIALIZER;
+
+ServerSharedBitmapManager::ServerSharedBitmapManager() = default;
+
+ServerSharedBitmapManager::~ServerSharedBitmapManager() {
+ DCHECK(handle_map_.empty());
+}
+
+ServerSharedBitmapManager* ServerSharedBitmapManager::current() {
+ return g_shared_memory_manager.Pointer();
+}
+
+std::unique_ptr<SharedBitmap> ServerSharedBitmapManager::AllocateSharedBitmap(
+ const gfx::Size& size) {
+ base::AutoLock lock(lock_);
+ size_t bitmap_size;
+ if (!SharedBitmap::SizeInBytes(size, &bitmap_size))
+ return nullptr;
+
+ scoped_refptr<BitmapData> data(new BitmapData(bitmap_size));
+ // Bitmaps allocated in server don't need to be shared to other processes, so
+ // allocate them with new instead.
+ data->pixels = std::unique_ptr<uint8_t[]>(new uint8_t[bitmap_size]);
+
+ SharedBitmapId id = SharedBitmap::GenerateId();
+ handle_map_[id] = data;
+ return base::MakeUnique<ServerSharedBitmap>(data->pixels.get(), data, id,
+ this);
+}
+
+std::unique_ptr<SharedBitmap> ServerSharedBitmapManager::GetSharedBitmapFromId(
+ const gfx::Size& size,
+ const SharedBitmapId& id) {
+ base::AutoLock lock(lock_);
+ auto it = handle_map_.find(id);
+ if (it == handle_map_.end())
+ return nullptr;
+
+ BitmapData* data = it->second.get();
+
+ size_t bitmap_size;
+ if (!SharedBitmap::SizeInBytes(size, &bitmap_size) ||
+ bitmap_size > data->buffer_size)
+ return nullptr;
+
+ if (data->pixels) {
+ return base::MakeUnique<ServerSharedBitmap>(data->pixels.get(), data, id,
+ nullptr);
+ }
+ if (!data->memory->memory()) {
+ return nullptr;
+ }
+
+ return base::MakeUnique<ServerSharedBitmap>(
+ static_cast<uint8_t*>(data->memory->memory()), data, id, nullptr);
+}
+
+bool ServerSharedBitmapManager::OnMemoryDump(
+ const base::trace_event::MemoryDumpArgs& args,
+ base::trace_event::ProcessMemoryDump* pmd) {
+ base::AutoLock lock(lock_);
+
+ for (const auto& bitmap : handle_map_) {
+ base::trace_event::MemoryAllocatorDump* dump =
+ pmd->CreateAllocatorDump(base::StringPrintf(
+ "sharedbitmap/%s",
+ base::HexEncode(bitmap.first.name, sizeof(bitmap.first.name))
+ .c_str()));
+ if (!dump)
+ return false;
+
+ dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
+ base::trace_event::MemoryAllocatorDump::kUnitsBytes,
+ bitmap.second->buffer_size);
+
+ // Generate a global GUID used to share this allocation with renderer
+ // processes.
+ auto guid = GetSharedBitmapGUIDForTracing(bitmap.first);
+ base::UnguessableToken shared_memory_guid;
+ if (bitmap.second->memory) {
+ shared_memory_guid = bitmap.second->memory->mapped_id();
+ if (!shared_memory_guid.is_empty()) {
+ pmd->CreateSharedMemoryOwnershipEdge(
+ dump->guid(), guid, shared_memory_guid, 0 /* importance*/);
+ }
+ } else {
+ pmd->CreateSharedGlobalAllocatorDump(guid);
+ pmd->AddOwnershipEdge(dump->guid(), guid);
+ }
+ }
+
+ return true;
+}
+
+bool ServerSharedBitmapManager::ChildAllocatedSharedBitmap(
+ size_t buffer_size,
+ const base::SharedMemoryHandle& handle,
+ const SharedBitmapId& id) {
+ base::AutoLock lock(lock_);
+ if (handle_map_.find(id) != handle_map_.end())
+ return false;
+ auto data = base::MakeRefCounted<BitmapData>(buffer_size);
+ data->memory = base::MakeUnique<base::SharedMemory>(handle, false);
+ data->memory->Map(data->buffer_size);
+ data->memory->Close();
+ handle_map_[id] = std::move(data);
+ return true;
+}
+
+void ServerSharedBitmapManager::ChildDeletedSharedBitmap(
+ const SharedBitmapId& id) {
+ base::AutoLock lock(lock_);
+ handle_map_.erase(id);
+}
+
+size_t ServerSharedBitmapManager::AllocatedBitmapCount() const {
+ base::AutoLock lock(lock_);
+ return handle_map_.size();
+}
+
+void ServerSharedBitmapManager::FreeSharedMemoryFromMap(
+ const SharedBitmapId& id) {
+ base::AutoLock lock(lock_);
+ handle_map_.erase(id);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager.h b/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager.h
new file mode 100644
index 00000000000..cd801a181e6
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2013 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_DISPLAY_EMBEDDER_SERVER_SHARED_BITMAP_MANAGER_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SERVER_SHARED_BITMAP_MANAGER_H_
+
+#include <memory>
+#include <unordered_map>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory.h"
+#include "base/synchronization/lock.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "components/viz/common/resources/shared_bitmap_manager.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+class BitmapData;
+
+// A SharedBitmapManager implementation that lives in-process with the
+// display compositor. It returns SharedBitmaps that can be transported
+// over Mojo/IPC to the display compositor, but which are backed by
+// non-shared memory. This is a cheaper implementation by using
+// malloc/free, but can only be used in the same process as the display
+// compositor.
+class VIZ_SERVICE_EXPORT ServerSharedBitmapManager
+ : public NON_EXPORTED_BASE(SharedBitmapManager),
+ public base::trace_event::MemoryDumpProvider {
+ public:
+ ServerSharedBitmapManager();
+ ~ServerSharedBitmapManager() override;
+
+ static ServerSharedBitmapManager* current();
+
+ // SharedBitmapManager implementation.
+ std::unique_ptr<SharedBitmap> AllocateSharedBitmap(
+ const gfx::Size& size) override;
+ std::unique_ptr<SharedBitmap> GetSharedBitmapFromId(
+ const gfx::Size& size,
+ const SharedBitmapId&) override;
+
+ // base::trace_event::MemoryDumpProvider implementation.
+ bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+ base::trace_event::ProcessMemoryDump* pmd) override;
+
+ size_t AllocatedBitmapCount() const;
+
+ void FreeSharedMemoryFromMap(const SharedBitmapId& id);
+
+ private:
+ friend class SharedBitmapAllocationNotifierImpl;
+
+ bool ChildAllocatedSharedBitmap(size_t buffer_size,
+ const base::SharedMemoryHandle& handle,
+ const SharedBitmapId& id);
+ void ChildDeletedSharedBitmap(const SharedBitmapId& id);
+
+ mutable base::Lock lock_;
+
+ std::unordered_map<SharedBitmapId,
+ scoped_refptr<BitmapData>,
+ SharedBitmapIdHash>
+ handle_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerSharedBitmapManager);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SERVER_SHARED_BITMAP_MANAGER_H_
diff --git a/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager_unittest.cc b/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager_unittest.cc
new file mode 100644
index 00000000000..0cf729b3d55
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/server_shared_bitmap_manager_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2013 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/display_embedder/server_shared_bitmap_manager.h"
+
+#include "base/memory/ptr_util.h"
+#include "components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace viz {
+namespace {
+
+class ServerSharedBitmapManagerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ manager_ = base::MakeUnique<ServerSharedBitmapManager>();
+ }
+
+ void TearDown() override { manager_.reset(); }
+
+ ServerSharedBitmapManager* manager() const { return manager_.get(); }
+
+ private:
+ std::unique_ptr<ServerSharedBitmapManager> manager_;
+};
+
+TEST_F(ServerSharedBitmapManagerTest, TestCreate) {
+ gfx::Size bitmap_size(1, 1);
+ size_t size_in_bytes;
+ EXPECT_TRUE(SharedBitmap::SizeInBytes(bitmap_size, &size_in_bytes));
+ std::unique_ptr<base::SharedMemory> bitmap(new base::SharedMemory());
+ bitmap->CreateAndMapAnonymous(size_in_bytes);
+ memset(bitmap->memory(), 0xff, size_in_bytes);
+ SharedBitmapId id = SharedBitmap::GenerateId();
+
+ SharedBitmapAllocationNotifierImpl notifier(manager());
+ base::SharedMemoryHandle handle = bitmap->handle().Duplicate();
+ notifier.ChildAllocatedSharedBitmap(size_in_bytes, handle, id);
+
+ std::unique_ptr<SharedBitmap> large_bitmap;
+ large_bitmap = manager()->GetSharedBitmapFromId(gfx::Size(1024, 1024), id);
+ EXPECT_TRUE(large_bitmap.get() == NULL);
+
+ std::unique_ptr<SharedBitmap> very_large_bitmap;
+ very_large_bitmap =
+ manager()->GetSharedBitmapFromId(gfx::Size(1, (1 << 30) | 1), id);
+ EXPECT_TRUE(very_large_bitmap.get() == NULL);
+
+ std::unique_ptr<SharedBitmap> negative_size_bitmap;
+ negative_size_bitmap =
+ manager()->GetSharedBitmapFromId(gfx::Size(-1, 1024), id);
+ EXPECT_TRUE(negative_size_bitmap.get() == NULL);
+
+ SharedBitmapId id2 = SharedBitmap::GenerateId();
+ std::unique_ptr<SharedBitmap> invalid_bitmap;
+ invalid_bitmap = manager()->GetSharedBitmapFromId(bitmap_size, id2);
+ EXPECT_TRUE(invalid_bitmap.get() == NULL);
+
+ std::unique_ptr<SharedBitmap> shared_bitmap;
+ shared_bitmap = manager()->GetSharedBitmapFromId(bitmap_size, id);
+ ASSERT_TRUE(shared_bitmap.get() != NULL);
+ EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), 4), 0);
+
+ std::unique_ptr<SharedBitmap> large_bitmap2;
+ large_bitmap2 = manager()->GetSharedBitmapFromId(gfx::Size(1024, 1024), id);
+ EXPECT_TRUE(large_bitmap2.get() == NULL);
+
+ std::unique_ptr<SharedBitmap> shared_bitmap2;
+ shared_bitmap2 = manager()->GetSharedBitmapFromId(bitmap_size, id);
+ EXPECT_TRUE(shared_bitmap2->pixels() == shared_bitmap->pixels());
+ shared_bitmap2.reset();
+ EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), size_in_bytes),
+ 0);
+
+ notifier.DidDeleteSharedBitmap(id);
+
+ memset(bitmap->memory(), 0, size_in_bytes);
+
+ EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), size_in_bytes),
+ 0);
+ bitmap.reset();
+ shared_bitmap.reset();
+}
+
+TEST_F(ServerSharedBitmapManagerTest, ServiceDestroyed) {
+ gfx::Size bitmap_size(1, 1);
+ size_t size_in_bytes;
+ EXPECT_TRUE(SharedBitmap::SizeInBytes(bitmap_size, &size_in_bytes));
+ std::unique_ptr<base::SharedMemory> bitmap(new base::SharedMemory());
+ bitmap->CreateAndMapAnonymous(size_in_bytes);
+ memset(bitmap->memory(), 0xff, size_in_bytes);
+ SharedBitmapId id = SharedBitmap::GenerateId();
+
+ // This outlives the SharedBitmapAllocationNotifier.
+ std::unique_ptr<SharedBitmap> shared_bitmap;
+
+ {
+ SharedBitmapAllocationNotifierImpl notifier(manager());
+ base::SharedMemoryHandle handle = bitmap->handle().Duplicate();
+ notifier.ChildAllocatedSharedBitmap(size_in_bytes, handle, id);
+
+ shared_bitmap = manager()->GetSharedBitmapFromId(bitmap_size, id);
+ ASSERT_TRUE(shared_bitmap.get() != NULL);
+
+ EXPECT_EQ(1u, manager()->AllocatedBitmapCount());
+ }
+ EXPECT_EQ(0u, manager()->AllocatedBitmapCount());
+
+ std::unique_ptr<SharedBitmap> shared_bitmap2;
+ shared_bitmap2 = manager()->GetSharedBitmapFromId(bitmap_size, id);
+ EXPECT_FALSE(!!shared_bitmap2);
+ EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), size_in_bytes),
+ 0);
+}
+
+TEST_F(ServerSharedBitmapManagerTest, AddDuplicate) {
+ gfx::Size bitmap_size(1, 1);
+ size_t size_in_bytes;
+ EXPECT_TRUE(SharedBitmap::SizeInBytes(bitmap_size, &size_in_bytes));
+ std::unique_ptr<base::SharedMemory> bitmap(new base::SharedMemory());
+ bitmap->CreateAndMapAnonymous(size_in_bytes);
+ memset(bitmap->memory(), 0xff, size_in_bytes);
+ SharedBitmapId id = SharedBitmap::GenerateId();
+
+ SharedBitmapAllocationNotifierImpl notifier(manager());
+
+ base::SharedMemoryHandle handle = bitmap->handle().Duplicate();
+ notifier.ChildAllocatedSharedBitmap(size_in_bytes, handle, id);
+
+ std::unique_ptr<base::SharedMemory> bitmap2(new base::SharedMemory());
+ bitmap2->CreateAndMapAnonymous(size_in_bytes);
+ memset(bitmap2->memory(), 0x00, size_in_bytes);
+
+ notifier.ChildAllocatedSharedBitmap(size_in_bytes, bitmap2->handle(), id);
+
+ std::unique_ptr<SharedBitmap> shared_bitmap;
+ shared_bitmap = manager()->GetSharedBitmapFromId(bitmap_size, id);
+ ASSERT_TRUE(shared_bitmap.get() != NULL);
+ EXPECT_EQ(memcmp(shared_bitmap->pixels(), bitmap->memory(), size_in_bytes),
+ 0);
+ notifier.DidDeleteSharedBitmap(id);
+}
+
+TEST_F(ServerSharedBitmapManagerTest, SharedMemoryHandle) {
+ gfx::Size bitmap_size(1, 1);
+ size_t size_in_bytes;
+ EXPECT_TRUE(SharedBitmap::SizeInBytes(bitmap_size, &size_in_bytes));
+ std::unique_ptr<base::SharedMemory> bitmap(new base::SharedMemory());
+ auto shared_memory_guid = bitmap->handle().GetGUID();
+ bitmap->CreateAndMapAnonymous(size_in_bytes);
+ memset(bitmap->memory(), 0xff, size_in_bytes);
+ SharedBitmapId id = SharedBitmap::GenerateId();
+
+ SharedBitmapAllocationNotifierImpl notifier(manager());
+
+ base::SharedMemoryHandle handle = bitmap->handle().Duplicate();
+ notifier.ChildAllocatedSharedBitmap(size_in_bytes, handle, id);
+
+ std::unique_ptr<SharedBitmap> shared_bitmap;
+ shared_bitmap = manager()->GetSharedBitmapFromId(gfx::Size(1, 1), id);
+ EXPECT_EQ(shared_bitmap->GetSharedMemoryHandle().GetGUID(),
+ shared_memory_guid);
+
+ notifier.DidDeleteSharedBitmap(id);
+}
+
+} // namespace
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.cc b/chromium/components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.cc
new file mode 100644
index 00000000000..d320a3dee75
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.cc
@@ -0,0 +1,85 @@
+// 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/display_embedder/shared_bitmap_allocation_notifier_impl.h"
+
+#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace viz {
+
+SharedBitmapAllocationNotifierImpl::SharedBitmapAllocationNotifierImpl(
+ ServerSharedBitmapManager* manager)
+ : manager_(manager), binding_(this) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+SharedBitmapAllocationNotifierImpl::~SharedBitmapAllocationNotifierImpl() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ ChildDied();
+}
+
+void SharedBitmapAllocationNotifierImpl::Bind(
+ cc::mojom::SharedBitmapAllocationNotifierRequest request) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ if (binding_.is_bound()) {
+ DLOG(ERROR) << "Only one SharedBitmapAllocationNotifierRequest is "
+ << "expected from the renderer.";
+ return;
+ }
+ binding_.Bind(std::move(request));
+}
+
+void SharedBitmapAllocationNotifierImpl::DidAllocateSharedBitmap(
+ mojo::ScopedSharedBufferHandle buffer,
+ const SharedBitmapId& id) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ base::SharedMemoryHandle memory_handle;
+ size_t size;
+ MojoResult result = mojo::UnwrapSharedMemoryHandle(
+ std::move(buffer), &memory_handle, &size, NULL);
+ DCHECK_EQ(result, MOJO_RESULT_OK);
+ this->ChildAllocatedSharedBitmap(size, memory_handle, id);
+ last_sequence_number_++;
+ for (SharedBitmapAllocationObserver& observer : observers_)
+ observer.DidAllocateSharedBitmap(last_sequence_number_);
+}
+
+void SharedBitmapAllocationNotifierImpl::DidDeleteSharedBitmap(
+ const SharedBitmapId& id) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ manager_->ChildDeletedSharedBitmap(id);
+ owned_bitmaps_.erase(id);
+}
+
+void SharedBitmapAllocationNotifierImpl::ChildAllocatedSharedBitmap(
+ size_t buffer_size,
+ const base::SharedMemoryHandle& handle,
+ const SharedBitmapId& id) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ if (manager_->ChildAllocatedSharedBitmap(buffer_size, handle, id))
+ owned_bitmaps_.insert(id);
+}
+
+void SharedBitmapAllocationNotifierImpl::ChildDied() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ for (const auto& id : owned_bitmaps_)
+ manager_->ChildDeletedSharedBitmap(id);
+ owned_bitmaps_.clear();
+ binding_.Close();
+}
+
+void SharedBitmapAllocationNotifierImpl::AddObserver(
+ SharedBitmapAllocationObserver* observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ observers_.AddObserver(observer);
+}
+
+void SharedBitmapAllocationNotifierImpl::RemoveObserver(
+ SharedBitmapAllocationObserver* observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.h b/chromium/components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.h
new file mode 100644
index 00000000000..4e9330a5759
--- /dev/null
+++ b/chromium/components/viz/service/display_embedder/shared_bitmap_allocation_notifier_impl.h
@@ -0,0 +1,64 @@
+// 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_DISPLAY_EMBEDDER_SHARED_BITMAP_ALLOCATION_NOTIFIER_IMPL_H_
+#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SHARED_BITMAP_ALLOCATION_NOTIFIER_IMPL_H_
+
+#include <unordered_set>
+
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "cc/ipc/shared_bitmap_allocation_notifier.mojom.h"
+#include "components/viz/common/quads/shared_bitmap.h"
+#include "components/viz/service/viz_service_export.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace viz {
+class ServerSharedBitmapManager;
+
+class SharedBitmapAllocationObserver {
+ public:
+ virtual void DidAllocateSharedBitmap(uint32_t sequence_number) = 0;
+};
+
+class VIZ_SERVICE_EXPORT SharedBitmapAllocationNotifierImpl
+ : NON_EXPORTED_BASE(public cc::mojom::SharedBitmapAllocationNotifier) {
+ public:
+ explicit SharedBitmapAllocationNotifierImpl(
+ ServerSharedBitmapManager* manager);
+
+ ~SharedBitmapAllocationNotifierImpl() override;
+
+ void AddObserver(SharedBitmapAllocationObserver* observer);
+ void RemoveObserver(SharedBitmapAllocationObserver* observer);
+
+ void Bind(cc::mojom::SharedBitmapAllocationNotifierRequest request);
+
+ // cc::mojom::SharedBitmapAllocationNotifier overrides:
+ void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+ const SharedBitmapId& id) override;
+ void DidDeleteSharedBitmap(const SharedBitmapId& id) override;
+
+ void ChildAllocatedSharedBitmap(size_t buffer_size,
+ const base::SharedMemoryHandle& handle,
+ const SharedBitmapId& id);
+
+ void ChildDied();
+
+ uint32_t last_sequence_number() const { return last_sequence_number_; }
+
+ private:
+ THREAD_CHECKER(thread_checker_);
+ ServerSharedBitmapManager* const manager_;
+ mojo::Binding<cc::mojom::SharedBitmapAllocationNotifier> binding_;
+ std::unordered_set<SharedBitmapId, SharedBitmapIdHash> owned_bitmaps_;
+ base::ObserverList<SharedBitmapAllocationObserver> observers_;
+ uint32_t last_sequence_number_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedBitmapAllocationNotifierImpl);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SHARED_BITMAP_ALLOCATION_NOTIFIER_IMPL_H_
diff --git a/chromium/components/viz/service/frame_sinks/DEPS b/chromium/components/viz/service/frame_sinks/DEPS
new file mode 100644
index 00000000000..af562e205c4
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ "+cc/base",
+ "+cc/ipc",
+ "+cc/surfaces",
+ "+cc/scheduler",
+ "+gpu/ipc/common",
+ "+mojo/public/cpp/bindings",
+]
diff --git a/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
new file mode 100644
index 00000000000..c13c567fc5b
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -0,0 +1,385 @@
+// Copyright 2016 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/frame_sinks/compositor_frame_sink_support.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "cc/output/compositor_frame.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/surface.h"
+#include "cc/surfaces/surface_reference.h"
+#include "components/viz/common/surfaces/surface_info.h"
+#include "components/viz/service/display/display.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support_client.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+
+namespace viz {
+
+// static
+std::unique_ptr<CompositorFrameSinkSupport> CompositorFrameSinkSupport::Create(
+ CompositorFrameSinkSupportClient* client,
+ FrameSinkManagerImpl* frame_sink_manager,
+ const FrameSinkId& frame_sink_id,
+ bool is_root,
+ bool handles_frame_sink_id_invalidation,
+ bool needs_sync_tokens) {
+ std::unique_ptr<CompositorFrameSinkSupport> support =
+ base::WrapUnique(new CompositorFrameSinkSupport(
+ client, frame_sink_id, is_root, handles_frame_sink_id_invalidation,
+ needs_sync_tokens));
+ support->Init(frame_sink_manager);
+ return support;
+}
+
+CompositorFrameSinkSupport::~CompositorFrameSinkSupport() {
+ if (!destruction_callback_.is_null())
+ std::move(destruction_callback_).Run();
+
+ // Unregister |this| as a cc::BeginFrameObserver so that the
+ // cc::BeginFrameSource does not call into |this| after it's deleted.
+ SetNeedsBeginFrame(false);
+
+ // For display root surfaces the surface is no longer going to be visible.
+ // Make it unreachable from the top-level root.
+ if (referenced_local_surface_id_.has_value()) {
+ auto reference = MakeTopLevelRootReference(
+ SurfaceId(frame_sink_id_, referenced_local_surface_id_.value()));
+ surface_manager_->RemoveSurfaceReferences({reference});
+ }
+
+ EvictCurrentSurface();
+ frame_sink_manager_->UnregisterFrameSinkManagerClient(frame_sink_id_);
+ if (handles_frame_sink_id_invalidation_)
+ surface_manager_->InvalidateFrameSinkId(frame_sink_id_);
+}
+
+void CompositorFrameSinkSupport::SetDestructionCallback(
+ base::OnceCallback<void()> callback) {
+ destruction_callback_ = std::move(callback);
+}
+
+void CompositorFrameSinkSupport::OnSurfaceActivated(cc::Surface* surface) {
+ DCHECK(surface->HasActiveFrame());
+ const cc::CompositorFrame& frame = surface->GetActiveFrame();
+ if (!seen_first_frame_activation_) {
+ // cc::SurfaceCreated only applies for the first cc::Surface activation.
+ seen_first_frame_activation_ = true;
+
+ gfx::Size frame_size = frame.render_pass_list.back()->output_rect.size();
+ surface_manager_->SurfaceCreated(SurfaceInfo(
+ surface->surface_id(), frame.metadata.device_scale_factor, frame_size));
+ }
+ // Fire cc::SurfaceCreated first so that a temporary reference is added before
+ // it is potentially transformed into a real reference by the client.
+ DCHECK(surface->active_referenced_surfaces());
+ UpdateSurfaceReferences(surface->surface_id().local_surface_id(),
+ *surface->active_referenced_surfaces());
+ if (!surface_manager_->SurfaceModified(surface->surface_id(),
+ frame.metadata.begin_frame_ack)) {
+ TRACE_EVENT_INSTANT0("cc", "Damage not visible.", TRACE_EVENT_SCOPE_THREAD);
+ surface->RunDrawCallback();
+ }
+ surface_manager_->SurfaceActivated(surface);
+}
+
+void CompositorFrameSinkSupport::RefResources(
+ const std::vector<cc::TransferableResource>& resources) {
+ surface_resource_holder_.RefResources(resources);
+}
+
+void CompositorFrameSinkSupport::UnrefResources(
+ const std::vector<cc::ReturnedResource>& resources) {
+ surface_resource_holder_.UnrefResources(resources);
+}
+
+void CompositorFrameSinkSupport::ReturnResources(
+ const std::vector<cc::ReturnedResource>& resources) {
+ if (resources.empty())
+ return;
+ if (!ack_pending_count_ && client_) {
+ client_->ReclaimResources(resources);
+ return;
+ }
+
+ std::copy(resources.begin(), resources.end(),
+ std::back_inserter(surface_returned_resources_));
+}
+
+void CompositorFrameSinkSupport::ReceiveFromChild(
+ const std::vector<cc::TransferableResource>& resources) {
+ surface_resource_holder_.ReceiveFromChild(resources);
+}
+
+void CompositorFrameSinkSupport::SetBeginFrameSource(
+ cc::BeginFrameSource* begin_frame_source) {
+ if (begin_frame_source_ && added_frame_observer_) {
+ begin_frame_source_->RemoveObserver(this);
+ added_frame_observer_ = false;
+ }
+ begin_frame_source_ = begin_frame_source;
+ UpdateNeedsBeginFramesInternal();
+}
+
+void CompositorFrameSinkSupport::EvictCurrentSurface() {
+ if (!current_surface_id_.is_valid())
+ return;
+ SurfaceId to_destroy_surface_id = current_surface_id_;
+ current_surface_id_ = SurfaceId();
+ surface_manager_->DestroySurface(to_destroy_surface_id);
+}
+
+void CompositorFrameSinkSupport::SetNeedsBeginFrame(bool needs_begin_frame) {
+ needs_begin_frame_ = needs_begin_frame;
+ UpdateNeedsBeginFramesInternal();
+}
+
+void CompositorFrameSinkSupport::DidNotProduceFrame(
+ const cc::BeginFrameAck& ack) {
+ TRACE_EVENT2("cc", "CompositorFrameSinkSupport::DidNotProduceFrame",
+ "ack.source_id", ack.source_id, "ack.sequence_number",
+ ack.sequence_number);
+ DCHECK_GE(ack.sequence_number, cc::BeginFrameArgs::kStartingFrameNumber);
+
+ // |has_damage| is not transmitted, but false by default.
+ DCHECK(!ack.has_damage);
+
+ if (current_surface_id_.is_valid())
+ surface_manager_->SurfaceModified(current_surface_id_, ack);
+
+ if (begin_frame_source_)
+ begin_frame_source_->DidFinishFrame(this);
+}
+
+bool CompositorFrameSinkSupport::SubmitCompositorFrame(
+ const LocalSurfaceId& local_surface_id,
+ cc::CompositorFrame frame) {
+ TRACE_EVENT0("cc", "CompositorFrameSinkSupport::SubmitCompositorFrame");
+ DCHECK(local_surface_id.is_valid());
+ DCHECK(!frame.render_pass_list.empty());
+
+ ++ack_pending_count_;
+
+ // |has_damage| is not transmitted.
+ frame.metadata.begin_frame_ack.has_damage = true;
+ cc::BeginFrameAck ack = frame.metadata.begin_frame_ack;
+ DCHECK_LE(cc::BeginFrameArgs::kStartingFrameNumber, ack.sequence_number);
+
+ if (!ui::LatencyInfo::Verify(frame.metadata.latency_info,
+ "RenderWidgetHostImpl::OnSwapCompositorFrame")) {
+ std::vector<ui::LatencyInfo>().swap(frame.metadata.latency_info);
+ }
+ for (ui::LatencyInfo& latency : frame.metadata.latency_info) {
+ if (latency.latency_components().size() > 0) {
+ latency.AddLatencyNumber(ui::DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT,
+ 0, 0);
+ }
+ }
+
+ cc::Surface* prev_surface =
+ surface_manager_->GetSurfaceForId(current_surface_id_);
+ cc::Surface* current_surface = nullptr;
+ if (prev_surface &&
+ local_surface_id == current_surface_id_.local_surface_id()) {
+ current_surface = prev_surface;
+ } else {
+ SurfaceId surface_id(frame_sink_id_, local_surface_id);
+ gfx::Size frame_size = frame.render_pass_list.back()->output_rect.size();
+ float device_scale_factor = frame.metadata.device_scale_factor;
+ SurfaceInfo surface_info(surface_id, device_scale_factor, frame_size);
+
+ if (!surface_info.is_valid()) {
+ TRACE_EVENT_INSTANT0("cc", "Invalid SurfaceInfo",
+ TRACE_EVENT_SCOPE_THREAD);
+ EvictCurrentSurface();
+ std::vector<cc::ReturnedResource> resources =
+ cc::TransferableResource::ReturnResources(frame.resource_list);
+ ReturnResources(resources);
+ DidReceiveCompositorFrameAck();
+ return true;
+ }
+
+ current_surface = CreateSurface(surface_info);
+ current_surface_id_ = SurfaceId(frame_sink_id_, local_surface_id);
+ surface_manager_->SurfaceDamageExpected(current_surface->surface_id(),
+ last_begin_frame_args_);
+ }
+
+ bool result = current_surface->QueueFrame(
+ std::move(frame),
+ base::Bind(&CompositorFrameSinkSupport::DidReceiveCompositorFrameAck,
+ weak_factory_.GetWeakPtr()),
+ base::BindRepeating(&CompositorFrameSinkSupport::WillDrawSurface,
+ weak_factory_.GetWeakPtr()));
+
+ if (!result) {
+ EvictCurrentSurface();
+ return false;
+ }
+
+ if (prev_surface && prev_surface != current_surface) {
+ current_surface->SetPreviousFrameSurface(prev_surface);
+ surface_manager_->DestroySurface(prev_surface->surface_id());
+ }
+
+ if (begin_frame_source_)
+ begin_frame_source_->DidFinishFrame(this);
+
+ return true;
+}
+
+void CompositorFrameSinkSupport::UpdateSurfaceReferences(
+ const LocalSurfaceId& local_surface_id,
+ const std::vector<SurfaceId>& active_referenced_surfaces) {
+ SurfaceId surface_id(frame_sink_id_, local_surface_id);
+
+ const base::flat_set<SurfaceId>& existing_referenced_surfaces =
+ surface_manager_->GetSurfacesReferencedByParent(surface_id);
+
+ base::flat_set<SurfaceId> new_referenced_surfaces(
+ active_referenced_surfaces.begin(), active_referenced_surfaces.end(),
+ base::KEEP_FIRST_OF_DUPES);
+
+ // Populate list of surface references to add and remove by getting the
+ // difference between existing surface references and surface references for
+ // latest activated CompositorFrame.
+ std::vector<cc::SurfaceReference> references_to_add;
+ std::vector<cc::SurfaceReference> references_to_remove;
+ GetSurfaceReferenceDifference(surface_id, existing_referenced_surfaces,
+ new_referenced_surfaces, &references_to_add,
+ &references_to_remove);
+
+ // Check if this is a display root surface and the SurfaceId is changing.
+ if (is_root_ && (!referenced_local_surface_id_.has_value() ||
+ referenced_local_surface_id_.value() != local_surface_id)) {
+ // Make the new SurfaceId reachable from the top-level root.
+ references_to_add.push_back(MakeTopLevelRootReference(surface_id));
+
+ // Make the old SurfaceId unreachable from the top-level root if applicable.
+ if (referenced_local_surface_id_.has_value()) {
+ references_to_remove.push_back(MakeTopLevelRootReference(
+ SurfaceId(frame_sink_id_, referenced_local_surface_id_.value())));
+ }
+
+ referenced_local_surface_id_ = local_surface_id;
+ }
+
+ // Modify surface references stored in cc::SurfaceManager.
+ if (!references_to_add.empty())
+ surface_manager_->AddSurfaceReferences(references_to_add);
+ if (!references_to_remove.empty())
+ surface_manager_->RemoveSurfaceReferences(references_to_remove);
+}
+
+cc::SurfaceReference CompositorFrameSinkSupport::MakeTopLevelRootReference(
+ const SurfaceId& surface_id) {
+ return cc::SurfaceReference(surface_manager_->GetRootSurfaceId(), surface_id);
+}
+
+void CompositorFrameSinkSupport::DidReceiveCompositorFrameAck() {
+ DCHECK_GT(ack_pending_count_, 0);
+ ack_pending_count_--;
+ if (!client_)
+ return;
+
+ client_->DidReceiveCompositorFrameAck(surface_returned_resources_);
+ surface_returned_resources_.clear();
+}
+
+void CompositorFrameSinkSupport::WillDrawSurface(
+ const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) {
+ if (client_)
+ client_->WillDrawSurface(local_surface_id, damage_rect);
+}
+
+void CompositorFrameSinkSupport::ClaimTemporaryReference(
+ const SurfaceId& surface_id) {
+ surface_manager_->AssignTemporaryReference(surface_id, frame_sink_id_);
+}
+
+CompositorFrameSinkSupport::CompositorFrameSinkSupport(
+ CompositorFrameSinkSupportClient* client,
+ const FrameSinkId& frame_sink_id,
+ bool is_root,
+ bool handles_frame_sink_id_invalidation,
+ bool needs_sync_tokens)
+ : client_(client),
+ frame_sink_id_(frame_sink_id),
+ surface_resource_holder_(this),
+ is_root_(is_root),
+ needs_sync_tokens_(needs_sync_tokens),
+ handles_frame_sink_id_invalidation_(handles_frame_sink_id_invalidation),
+ weak_factory_(this) {}
+
+void CompositorFrameSinkSupport::Init(
+ FrameSinkManagerImpl* frame_sink_manager) {
+ frame_sink_manager_ = frame_sink_manager;
+ surface_manager_ = frame_sink_manager->surface_manager();
+ if (handles_frame_sink_id_invalidation_)
+ surface_manager_->RegisterFrameSinkId(frame_sink_id_);
+ frame_sink_manager_->RegisterFrameSinkManagerClient(frame_sink_id_, this);
+}
+
+void CompositorFrameSinkSupport::OnBeginFrame(const cc::BeginFrameArgs& args) {
+ UpdateNeedsBeginFramesInternal();
+ if (current_surface_id_.is_valid()) {
+ surface_manager_->SurfaceDamageExpected(current_surface_id_, args);
+ }
+ last_begin_frame_args_ = args;
+ if (client_)
+ client_->OnBeginFrame(args);
+}
+
+const cc::BeginFrameArgs& CompositorFrameSinkSupport::LastUsedBeginFrameArgs()
+ const {
+ return last_begin_frame_args_;
+}
+
+void CompositorFrameSinkSupport::OnBeginFrameSourcePausedChanged(bool paused) {
+ if (client_)
+ client_->OnBeginFramePausedChanged(paused);
+}
+
+void CompositorFrameSinkSupport::UpdateNeedsBeginFramesInternal() {
+ if (!begin_frame_source_)
+ return;
+
+ if (needs_begin_frame_ == added_frame_observer_)
+ return;
+
+ added_frame_observer_ = needs_begin_frame_;
+ if (needs_begin_frame_)
+ begin_frame_source_->AddObserver(this);
+ else
+ begin_frame_source_->RemoveObserver(this);
+}
+
+cc::Surface* CompositorFrameSinkSupport::CreateSurface(
+ const SurfaceInfo& surface_info) {
+ seen_first_frame_activation_ = false;
+ return surface_manager_->CreateSurface(
+ weak_factory_.GetWeakPtr(), surface_info,
+ frame_sink_manager_->GetPrimaryBeginFrameSource(), needs_sync_tokens_);
+}
+
+void CompositorFrameSinkSupport::RequestCopyOfSurface(
+ std::unique_ptr<cc::CopyOutputRequest> copy_request) {
+ if (!current_surface_id_.is_valid())
+ return;
+ cc::Surface* current_surface =
+ surface_manager_->GetSurfaceForId(current_surface_id_);
+ current_surface->RequestCopyOfOutput(std::move(copy_request));
+ cc::BeginFrameAck ack;
+ ack.has_damage = true;
+ if (current_surface->HasActiveFrame())
+ surface_manager_->SurfaceModified(current_surface->surface_id(), ack);
+}
+
+cc::Surface* CompositorFrameSinkSupport::GetCurrentSurfaceForTesting() {
+ return surface_manager_->GetSurfaceForId(current_surface_id_);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support.h
new file mode 100644
index 00000000000..ccf7f398c64
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -0,0 +1,173 @@
+// Copyright 2016 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_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_H_
+
+#include <memory>
+#include <unordered_set>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/surfaces/surface_client.h"
+#include "components/viz/common/surfaces/surface_info.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_client.h"
+#include "components/viz/service/frame_sinks/referenced_surface_tracker.h"
+#include "components/viz/service/frame_sinks/surface_resource_holder.h"
+#include "components/viz/service/frame_sinks/surface_resource_holder_client.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace cc {
+class Surface;
+class SurfaceManager;
+} // namespace cc
+
+namespace viz {
+
+class FrameSinkManagerImpl;
+class CompositorFrameSinkSupportClient;
+
+class VIZ_SERVICE_EXPORT CompositorFrameSinkSupport
+ : public cc::BeginFrameObserver,
+ public SurfaceResourceHolderClient,
+ public FrameSinkManagerClient,
+ public cc::SurfaceClient {
+ public:
+ static std::unique_ptr<CompositorFrameSinkSupport> Create(
+ CompositorFrameSinkSupportClient* client,
+ FrameSinkManagerImpl* frame_sink_manager,
+ const FrameSinkId& frame_sink_id,
+ bool is_root,
+ bool handles_frame_sink_id_invalidation,
+ bool needs_sync_tokens);
+
+ ~CompositorFrameSinkSupport() override;
+
+ const FrameSinkId& frame_sink_id() const { return frame_sink_id_; }
+
+ FrameSinkManagerImpl* frame_sink_manager() { return frame_sink_manager_; }
+ cc::SurfaceManager* surface_manager() { return surface_manager_; }
+
+ void SetDestructionCallback(base::OnceCallback<void()> callback);
+
+ // SurfaceClient implementation.
+ void OnSurfaceActivated(cc::Surface* surface) override;
+ void RefResources(
+ const std::vector<cc::TransferableResource>& resources) override;
+ void UnrefResources(
+ const std::vector<cc::ReturnedResource>& resources) override;
+ void ReturnResources(
+ const std::vector<cc::ReturnedResource>& resources) override;
+ void ReceiveFromChild(
+ const std::vector<cc::TransferableResource>& resources) override;
+
+ // FrameSinkManagerClient implementation.
+ void SetBeginFrameSource(cc::BeginFrameSource* begin_frame_source) override;
+
+ void EvictCurrentSurface();
+ void SetNeedsBeginFrame(bool needs_begin_frame);
+ void DidNotProduceFrame(const cc::BeginFrameAck& ack);
+ bool SubmitCompositorFrame(const LocalSurfaceId& local_surface_id,
+ cc::CompositorFrame frame);
+ void RequestCopyOfSurface(std::unique_ptr<cc::CopyOutputRequest> request);
+ void ClaimTemporaryReference(const SurfaceId& surface_id);
+
+ cc::Surface* GetCurrentSurfaceForTesting();
+
+ protected:
+ CompositorFrameSinkSupport(CompositorFrameSinkSupportClient* client,
+ const FrameSinkId& frame_sink_id,
+ bool is_root,
+ bool handles_frame_sink_id_invalidation,
+ bool needs_sync_tokens);
+
+ void Init(FrameSinkManagerImpl* frame_sink_manager);
+
+ private:
+ // Updates surface references using |active_referenced_surfaces| from the most
+ // recent CompositorFrame. This will add and remove top-level root references
+ // if |is_root_| is true and |local_surface_id| has changed. Modifies surface
+ // references stored in SurfaceManager.
+ void UpdateSurfaceReferences(
+ const LocalSurfaceId& local_surface_id,
+ const std::vector<SurfaceId>& active_referenced_surfaces);
+
+ // Creates a surface reference from the top-level root to |surface_id|.
+ cc::SurfaceReference MakeTopLevelRootReference(const SurfaceId& surface_id);
+
+ void DidReceiveCompositorFrameAck();
+ void WillDrawSurface(const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect);
+
+ // BeginFrameObserver implementation.
+ void OnBeginFrame(const cc::BeginFrameArgs& args) override;
+ const cc::BeginFrameArgs& LastUsedBeginFrameArgs() const override;
+ void OnBeginFrameSourcePausedChanged(bool paused) override;
+
+ void UpdateNeedsBeginFramesInternal();
+ cc::Surface* CreateSurface(const SurfaceInfo& surface_info);
+
+ CompositorFrameSinkSupportClient* const client_;
+
+ FrameSinkManagerImpl* frame_sink_manager_ = nullptr;
+ cc::SurfaceManager* surface_manager_ = nullptr;
+
+ const FrameSinkId frame_sink_id_;
+ SurfaceId current_surface_id_;
+
+ // If this contains a value then a surface reference from the top-level root
+ // to SurfaceId(frame_sink_id_, referenced_local_surface_id_.value()) was
+ // added. This will not contain a value if |is_root_| is false.
+ base::Optional<LocalSurfaceId> referenced_local_surface_id_;
+
+ SurfaceResourceHolder surface_resource_holder_;
+
+ // Counts the number of CompositorFrames that have been submitted and have not
+ // yet received an ACK.
+ int ack_pending_count_ = 0;
+ std::vector<cc::ReturnedResource> surface_returned_resources_;
+
+ // The begin frame source being observered. Null if none.
+ cc::BeginFrameSource* begin_frame_source_ = nullptr;
+
+ // The last begin frame args generated by the begin frame source.
+ cc::BeginFrameArgs last_begin_frame_args_;
+
+ // Whether a request for begin frames has been issued.
+ bool needs_begin_frame_ = false;
+
+ // Whether or not a frame observer has been added.
+ bool added_frame_observer_ = false;
+
+ const bool is_root_;
+ const bool needs_sync_tokens_;
+ bool seen_first_frame_activation_ = false;
+
+ // TODO(staraz): Remove this flag once ui::Compositor no longer needs to call
+ // RegisterFrameSinkId().
+ // A surfaceSequence's validity is bound to the lifetime of the parent
+ // FrameSink that created it. We track the lifetime of FrameSinks through
+ // RegisterFrameSinkId and InvalidateFrameSinkId. During startup and GPU
+ // restart, a SurfaceSequence created by the top most layer compositor may be
+ // used prior to the creation of the associated CompositorFrameSinkSupport.
+ // CompositorFrameSinkSupport is created asynchronously when a new GPU channel
+ // is established. Once we switch to SurfaceReferences, this ordering concern
+ // goes away and we can remove this bool.
+ const bool handles_frame_sink_id_invalidation_;
+
+ // A callback that will be run at the start of the destructor if set.
+ base::OnceCallback<void()> destruction_callback_;
+
+ base::WeakPtrFactory<CompositorFrameSinkSupport> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompositorFrameSinkSupport);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_H_
diff --git a/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_client.h b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_client.h
new file mode 100644
index 00000000000..792bd9dcb1d
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_client.h
@@ -0,0 +1,53 @@
+// Copyright 2016 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_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_CLIENT_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_CLIENT_H_
+
+#include "cc/resources/returned_resource.h"
+
+namespace cc {
+struct BeginFrameArgs;
+} // namespace cc
+
+namespace gfx {
+class Rect;
+}
+
+namespace viz {
+class LocalSurfaceId;
+
+class CompositorFrameSinkSupportClient {
+ public:
+ // Notification that the previous CompositorFrame given to
+ // SubmitCompositorFrame() has been processed and that another frame
+ // can be submitted. This provides backpressure from the display compositor
+ // so that frames are submitted only at the rate it can handle them.
+ // TODO(fsamuel): This method ought not be necessary with unified BeginFrame.
+ // However, there's a fair amount of cleanup and refactoring necessary to get
+ // rid of it.
+ virtual void DidReceiveCompositorFrameAck(
+ const std::vector<cc::ReturnedResource>& resources) = 0;
+
+ // Notification for the client to generate a CompositorFrame.
+ virtual void OnBeginFrame(const cc::BeginFrameArgs& args) = 0;
+
+ // Returns resources sent to SubmitCompositorFrame to be reused or freed.
+ virtual void ReclaimResources(
+ const std::vector<cc::ReturnedResource>& resources) = 0;
+
+ // Called when surface is being scheduled for a draw.
+ virtual void WillDrawSurface(const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) = 0;
+
+ // Notification that there may not be OnBeginFrame calls for some time.
+ virtual void OnBeginFramePausedChanged(bool paused) = 0;
+
+ protected:
+ virtual ~CompositorFrameSinkSupportClient() {}
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_CLIENT_H_
diff --git a/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_manager.h b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_manager.h
new file mode 100644
index 00000000000..91a4e805709
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_manager.h
@@ -0,0 +1,35 @@
+// 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_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_MANAGER_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_MANAGER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+
+namespace viz {
+
+class CompositorFrameSinkSupport;
+class CompositorFrameSinkSupportClient;
+class FrameSinkId;
+
+// This inteface provides a way for DirectLayerTreeFrameSink to create a
+// CompositorFrameSinkSupport.
+class CompositorFrameSinkSupportManager {
+ public:
+ virtual std::unique_ptr<CompositorFrameSinkSupport>
+ CreateCompositorFrameSinkSupport(CompositorFrameSinkSupportClient* client,
+ const FrameSinkId& frame_sink_id,
+ bool is_root,
+ bool handles_frame_sink_id_invalidation,
+ bool needs_sync_points) = 0;
+
+ protected:
+ virtual ~CompositorFrameSinkSupportManager() {}
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_SUPPORT_MANAGER_H_
diff --git a/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
new file mode 100644
index 00000000000..e876d654a54
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
@@ -0,0 +1,894 @@
+// 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/frame_sinks/compositor_frame_sink_support.h"
+
+#include "base/macros.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/test/begin_frame_args_test.h"
+#include "cc/test/compositor_frame_helpers.h"
+#include "cc/test/fake_external_begin_frame_source.h"
+#include "cc/test/fake_surface_observer.h"
+#include "cc/test/mock_compositor_frame_sink_support_client.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/common/surfaces/surface_info.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support_client.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::UnorderedElementsAre;
+using testing::IsEmpty;
+using testing::SizeIs;
+using testing::Invoke;
+using testing::_;
+using testing::Eq;
+
+namespace viz {
+namespace {
+
+constexpr bool kIsRoot = true;
+constexpr bool kIsChildRoot = false;
+constexpr bool kHandlesFrameSinkIdInvalidation = true;
+constexpr bool kNeedsSyncPoints = true;
+
+constexpr FrameSinkId kArbitraryFrameSinkId(1, 1);
+constexpr FrameSinkId kAnotherArbitraryFrameSinkId(2, 2);
+constexpr FrameSinkId kYetAnotherArbitraryFrameSinkId(3, 3);
+
+const base::UnguessableToken kArbitraryToken = base::UnguessableToken::Create();
+const base::UnguessableToken kArbitrarySourceId1 =
+ base::UnguessableToken::Deserialize(0xdead, 0xbeef);
+const base::UnguessableToken kArbitrarySourceId2 =
+ base::UnguessableToken::Deserialize(0xdead, 0xbee0);
+
+gpu::SyncToken GenTestSyncToken(int id) {
+ gpu::SyncToken token;
+ token.Set(gpu::CommandBufferNamespace::GPU_IO, 0,
+ gpu::CommandBufferId::FromUnsafeValue(id), 1);
+ return token;
+}
+
+class FakeCompositorFrameSinkSupportClient
+ : public CompositorFrameSinkSupportClient {
+ public:
+ FakeCompositorFrameSinkSupportClient() = default;
+ ~FakeCompositorFrameSinkSupportClient() override = default;
+
+ void DidReceiveCompositorFrameAck(
+ const std::vector<cc::ReturnedResource>& resources) override {
+ InsertResources(resources);
+ }
+
+ void OnBeginFrame(const cc::BeginFrameArgs& args) override {}
+
+ void ReclaimResources(
+ const std::vector<cc::ReturnedResource>& resources) override {
+ InsertResources(resources);
+ }
+
+ void WillDrawSurface(const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) override {}
+
+ void OnBeginFramePausedChanged(bool paused) override {}
+
+ void clear_returned_resources() { returned_resources_.clear(); }
+ const std::vector<cc::ReturnedResource>& returned_resources() {
+ return returned_resources_;
+ }
+
+ private:
+ void InsertResources(const std::vector<cc::ReturnedResource>& resources) {
+ returned_resources_.insert(returned_resources_.end(), resources.begin(),
+ resources.end());
+ }
+
+ std::vector<cc::ReturnedResource> returned_resources_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeCompositorFrameSinkSupportClient);
+};
+
+class CompositorFrameSinkSupportTest : public testing::Test {
+ public:
+ CompositorFrameSinkSupportTest()
+ : support_(
+ CompositorFrameSinkSupport::Create(&fake_support_client_,
+ &manager_,
+ kArbitraryFrameSinkId,
+ kIsRoot,
+ kHandlesFrameSinkIdInvalidation,
+ kNeedsSyncPoints)),
+ begin_frame_source_(0.f, false),
+ local_surface_id_(3, kArbitraryToken),
+ frame_sync_token_(GenTestSyncToken(4)),
+ consumer_sync_token_(GenTestSyncToken(5)) {
+ manager_.surface_manager()->AddObserver(&surface_observer_);
+ support_->SetBeginFrameSource(&begin_frame_source_);
+ }
+ ~CompositorFrameSinkSupportTest() override {
+ manager_.surface_manager()->RemoveObserver(&surface_observer_);
+ support_->EvictCurrentSurface();
+ }
+
+ void SubmitCompositorFrameWithResources(cc::ResourceId* resource_ids,
+ size_t num_resource_ids) {
+ auto frame = cc::test::MakeCompositorFrame();
+ for (size_t i = 0u; i < num_resource_ids; ++i) {
+ cc::TransferableResource resource;
+ resource.id = resource_ids[i];
+ resource.mailbox_holder.texture_target = GL_TEXTURE_2D;
+ resource.mailbox_holder.sync_token = frame_sync_token_;
+ frame.resource_list.push_back(resource);
+ }
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame));
+ EXPECT_EQ(surface_observer_.last_created_surface_id().local_surface_id(),
+ local_surface_id_);
+ }
+
+ void UnrefResources(cc::ResourceId* ids_to_unref,
+ int* counts_to_unref,
+ size_t num_ids_to_unref) {
+ std::vector<cc::ReturnedResource> unref_array;
+ for (size_t i = 0; i < num_ids_to_unref; ++i) {
+ cc::ReturnedResource resource;
+ resource.sync_token = consumer_sync_token_;
+ resource.id = ids_to_unref[i];
+ resource.count = counts_to_unref[i];
+ unref_array.push_back(resource);
+ }
+ support_->UnrefResources(unref_array);
+ }
+
+ void CheckReturnedResourcesMatchExpected(
+ cc::ResourceId* expected_returned_ids,
+ int* expected_returned_counts,
+ size_t expected_resources,
+ gpu::SyncToken expected_sync_token) {
+ const std::vector<cc::ReturnedResource>& actual_resources =
+ fake_support_client_.returned_resources();
+ ASSERT_EQ(expected_resources, actual_resources.size());
+ for (size_t i = 0; i < expected_resources; ++i) {
+ cc::ReturnedResource resource = actual_resources[i];
+ EXPECT_EQ(expected_sync_token, resource.sync_token);
+ EXPECT_EQ(expected_returned_ids[i], resource.id);
+ EXPECT_EQ(expected_returned_counts[i], resource.count);
+ }
+ fake_support_client_.clear_returned_resources();
+ }
+
+ cc::Surface* GetSurfaceForId(const SurfaceId& id) {
+ return manager_.surface_manager()->GetSurfaceForId(id);
+ }
+
+ void RefCurrentFrameResources() {
+ cc::Surface* surface = GetSurfaceForId(
+ SurfaceId(support_->frame_sink_id(), local_surface_id_));
+ support_->RefResources(surface->GetActiveFrame().resource_list);
+ }
+
+ protected:
+ FrameSinkManagerImpl manager_;
+ FakeCompositorFrameSinkSupportClient fake_support_client_;
+ std::unique_ptr<CompositorFrameSinkSupport> support_;
+ cc::FakeExternalBeginFrameSource begin_frame_source_;
+ LocalSurfaceId local_surface_id_;
+ cc::FakeSurfaceObserver surface_observer_;
+
+ // This is the sync token submitted with the frame. It should never be
+ // returned to the client.
+ const gpu::SyncToken frame_sync_token_;
+
+ // This is the sync token returned by the consumer. It should always be
+ // returned to the client.
+ const gpu::SyncToken consumer_sync_token_;
+};
+
+// Tests submitting a frame with resources followed by one with no resources
+// with no resource provider action in between.
+TEST_F(CompositorFrameSinkSupportTest, ResourceLifetimeSimple) {
+ cc::ResourceId first_frame_ids[] = {1, 2, 3};
+ SubmitCompositorFrameWithResources(first_frame_ids,
+ arraysize(first_frame_ids));
+
+ // All of the resources submitted in the first frame are still in use at this
+ // time by virtue of being in the pending frame, so none can be returned to
+ // the client yet.
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+ fake_support_client_.clear_returned_resources();
+
+ // The second frame references no resources of first frame and thus should
+ // make all resources of first frame available to be returned.
+ SubmitCompositorFrameWithResources(NULL, 0);
+
+ cc::ResourceId expected_returned_ids[] = {1, 2, 3};
+ int expected_returned_counts[] = {1, 1, 1};
+ // Resources were never consumed so no sync token should be set.
+ CheckReturnedResourcesMatchExpected(
+ expected_returned_ids, expected_returned_counts,
+ arraysize(expected_returned_counts), gpu::SyncToken());
+
+ cc::ResourceId third_frame_ids[] = {4, 5, 6};
+ SubmitCompositorFrameWithResources(third_frame_ids,
+ arraysize(third_frame_ids));
+
+ // All of the resources submitted in the third frame are still in use at this
+ // time by virtue of being in the pending frame, so none can be returned to
+ // the client yet.
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+ fake_support_client_.clear_returned_resources();
+
+ // The forth frame references no resources of third frame and thus should
+ // make all resources of third frame available to be returned.
+ cc::ResourceId forth_frame_ids[] = {7, 8, 9};
+ SubmitCompositorFrameWithResources(forth_frame_ids,
+ arraysize(forth_frame_ids));
+
+ cc::ResourceId forth_expected_returned_ids[] = {4, 5, 6};
+ int forth_expected_returned_counts[] = {1, 1, 1};
+ // Resources were never consumed so no sync token should be set.
+ CheckReturnedResourcesMatchExpected(
+ forth_expected_returned_ids, forth_expected_returned_counts,
+ arraysize(forth_expected_returned_counts), gpu::SyncToken());
+}
+
+// Tests submitting a frame with resources followed by one with no resources
+// with the resource provider holding everything alive.
+TEST_F(CompositorFrameSinkSupportTest,
+ ResourceLifetimeSimpleWithProviderHoldingAlive) {
+ cc::ResourceId first_frame_ids[] = {1, 2, 3};
+ SubmitCompositorFrameWithResources(first_frame_ids,
+ arraysize(first_frame_ids));
+
+ // All of the resources submitted in the first frame are still in use at this
+ // time by virtue of being in the pending frame, so none can be returned to
+ // the client yet.
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+ fake_support_client_.clear_returned_resources();
+
+ // Hold on to everything.
+ RefCurrentFrameResources();
+
+ // The second frame references no resources and thus should make all resources
+ // available to be returned as soon as the resource provider releases them.
+ SubmitCompositorFrameWithResources(NULL, 0);
+
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+ fake_support_client_.clear_returned_resources();
+
+ int release_counts[] = {1, 1, 1};
+ UnrefResources(first_frame_ids, release_counts, arraysize(first_frame_ids));
+
+ // None is returned to the client since DidReceiveCompositorAck is not
+ // invoked.
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+
+ // Submitting an empty frame causes previous resources referenced by the
+ // previous frame to be returned to client.
+ SubmitCompositorFrameWithResources(nullptr, 0);
+ cc::ResourceId expected_returned_ids[] = {1, 2, 3};
+ int expected_returned_counts[] = {1, 1, 1};
+ CheckReturnedResourcesMatchExpected(
+ expected_returned_ids, expected_returned_counts,
+ arraysize(expected_returned_counts), consumer_sync_token_);
+}
+
+// Tests referencing a resource, unref'ing it to zero, then using it again
+// before returning it to the client.
+TEST_F(CompositorFrameSinkSupportTest, ResourceReusedBeforeReturn) {
+ cc::ResourceId first_frame_ids[] = {7};
+ SubmitCompositorFrameWithResources(first_frame_ids,
+ arraysize(first_frame_ids));
+
+ // This removes all references to resource id 7.
+ SubmitCompositorFrameWithResources(NULL, 0);
+
+ // This references id 7 again.
+ SubmitCompositorFrameWithResources(first_frame_ids,
+ arraysize(first_frame_ids));
+
+ // This removes it again.
+ SubmitCompositorFrameWithResources(NULL, 0);
+
+ // Now it should be returned.
+ // We don't care how many entries are in the returned array for 7, so long as
+ // the total returned count matches the submitted count.
+ const std::vector<cc::ReturnedResource>& returned =
+ fake_support_client_.returned_resources();
+ size_t return_count = 0;
+ for (size_t i = 0; i < returned.size(); ++i) {
+ EXPECT_EQ(7u, returned[i].id);
+ return_count += returned[i].count;
+ }
+ EXPECT_EQ(2u, return_count);
+}
+
+// Tests having resources referenced multiple times, as if referenced by
+// multiple providers.
+TEST_F(CompositorFrameSinkSupportTest, ResourceRefMultipleTimes) {
+ cc::ResourceId first_frame_ids[] = {3, 4};
+ SubmitCompositorFrameWithResources(first_frame_ids,
+ arraysize(first_frame_ids));
+
+ // Ref resources from the first frame twice.
+ RefCurrentFrameResources();
+ RefCurrentFrameResources();
+
+ cc::ResourceId second_frame_ids[] = {4, 5};
+ SubmitCompositorFrameWithResources(second_frame_ids,
+ arraysize(second_frame_ids));
+
+ // Ref resources from the second frame 3 times.
+ RefCurrentFrameResources();
+ RefCurrentFrameResources();
+ RefCurrentFrameResources();
+
+ // Submit a frame with no resources to remove all current frame refs from
+ // submitted resources.
+ SubmitCompositorFrameWithResources(NULL, 0);
+
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+ fake_support_client_.clear_returned_resources();
+
+ // Expected current refs:
+ // 3 -> 2
+ // 4 -> 2 + 3 = 5
+ // 5 -> 3
+ {
+ SCOPED_TRACE("unref all 3");
+ cc::ResourceId ids_to_unref[] = {3, 4, 5};
+ int counts[] = {1, 1, 1};
+ UnrefResources(ids_to_unref, counts, arraysize(ids_to_unref));
+
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+ fake_support_client_.clear_returned_resources();
+
+ UnrefResources(ids_to_unref, counts, arraysize(ids_to_unref));
+ SubmitCompositorFrameWithResources(nullptr, 0);
+ cc::ResourceId expected_returned_ids[] = {3};
+ int expected_returned_counts[] = {1};
+ CheckReturnedResourcesMatchExpected(
+ expected_returned_ids, expected_returned_counts,
+ arraysize(expected_returned_counts), consumer_sync_token_);
+ }
+
+ // Expected refs remaining:
+ // 4 -> 3
+ // 5 -> 1
+ {
+ SCOPED_TRACE("unref 4 and 5");
+ cc::ResourceId ids_to_unref[] = {4, 5};
+ int counts[] = {1, 1};
+ UnrefResources(ids_to_unref, counts, arraysize(ids_to_unref));
+ SubmitCompositorFrameWithResources(nullptr, 0);
+
+ cc::ResourceId expected_returned_ids[] = {5};
+ int expected_returned_counts[] = {1};
+ CheckReturnedResourcesMatchExpected(
+ expected_returned_ids, expected_returned_counts,
+ arraysize(expected_returned_counts), consumer_sync_token_);
+ }
+
+ // Now, just 2 refs remaining on resource 4. Unref both at once and make sure
+ // the returned count is correct.
+ {
+ SCOPED_TRACE("unref only 4");
+ cc::ResourceId ids_to_unref[] = {4};
+ int counts[] = {2};
+ UnrefResources(ids_to_unref, counts, arraysize(ids_to_unref));
+ SubmitCompositorFrameWithResources(nullptr, 0);
+
+ cc::ResourceId expected_returned_ids[] = {4};
+ int expected_returned_counts[] = {2};
+ CheckReturnedResourcesMatchExpected(
+ expected_returned_ids, expected_returned_counts,
+ arraysize(expected_returned_counts), consumer_sync_token_);
+ }
+}
+
+TEST_F(CompositorFrameSinkSupportTest, ResourceLifetime) {
+ cc::ResourceId first_frame_ids[] = {1, 2, 3};
+ SubmitCompositorFrameWithResources(first_frame_ids,
+ arraysize(first_frame_ids));
+
+ // All of the resources submitted in the first frame are still in use at this
+ // time by virtue of being in the pending frame, so none can be returned to
+ // the client yet.
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+ fake_support_client_.clear_returned_resources();
+
+ // The second frame references some of the same resources, but some different
+ // ones. We expect to receive back resource 1 with a count of 1 since it was
+ // only referenced by the first frame.
+ cc::ResourceId second_frame_ids[] = {2, 3, 4};
+ SubmitCompositorFrameWithResources(second_frame_ids,
+ arraysize(second_frame_ids));
+ {
+ SCOPED_TRACE("second frame");
+ cc::ResourceId expected_returned_ids[] = {1};
+ int expected_returned_counts[] = {1};
+ CheckReturnedResourcesMatchExpected(
+ expected_returned_ids, expected_returned_counts,
+ arraysize(expected_returned_counts), gpu::SyncToken());
+ }
+
+ // The third frame references a disjoint set of resources, so we expect to
+ // receive back all resources from the first and second frames. Resource IDs 2
+ // and 3 will have counts of 2, since they were used in both frames, and
+ // resource ID 4 will have a count of 1.
+ cc::ResourceId third_frame_ids[] = {10, 11, 12, 13};
+ SubmitCompositorFrameWithResources(third_frame_ids,
+ arraysize(third_frame_ids));
+
+ {
+ SCOPED_TRACE("third frame");
+ cc::ResourceId expected_returned_ids[] = {2, 3, 4};
+ int expected_returned_counts[] = {2, 2, 1};
+ CheckReturnedResourcesMatchExpected(
+ expected_returned_ids, expected_returned_counts,
+ arraysize(expected_returned_counts), gpu::SyncToken());
+ }
+
+ // Simulate a ResourceProvider taking a ref on all of the resources.
+ RefCurrentFrameResources();
+
+ cc::ResourceId fourth_frame_ids[] = {12, 13};
+ SubmitCompositorFrameWithResources(fourth_frame_ids,
+ arraysize(fourth_frame_ids));
+
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+
+ RefCurrentFrameResources();
+
+ // All resources are still being used by the external reference, so none can
+ // be returned to the client.
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+
+ // Release resources associated with the first RefCurrentFrameResources() call
+ // first.
+ {
+ cc::ResourceId ids_to_unref[] = {10, 11, 12, 13};
+ int counts[] = {1, 1, 1, 1};
+ UnrefResources(ids_to_unref, counts, arraysize(ids_to_unref));
+ }
+
+ // Nothing is returned to the client yet since DidReceiveCompositorFrameAck
+ // is not invoked.
+ {
+ SCOPED_TRACE("fourth frame, first unref");
+ CheckReturnedResourcesMatchExpected(nullptr, nullptr, 0,
+ consumer_sync_token_);
+ }
+
+ {
+ cc::ResourceId ids_to_unref[] = {12, 13};
+ int counts[] = {1, 1};
+ UnrefResources(ids_to_unref, counts, arraysize(ids_to_unref));
+ }
+
+ // Resources 12 and 13 are still in use by the current frame, so they
+ // shouldn't be available to be returned.
+ EXPECT_EQ(0u, fake_support_client_.returned_resources().size());
+
+ // If we submit an empty frame, however, they should become available.
+ // Resources that were previously unref'd also return at this point.
+ SubmitCompositorFrameWithResources(NULL, 0u);
+
+ {
+ SCOPED_TRACE("fourth frame, second unref");
+ cc::ResourceId expected_returned_ids[] = {10, 11, 12, 13};
+ int expected_returned_counts[] = {1, 1, 2, 2};
+ CheckReturnedResourcesMatchExpected(
+ expected_returned_ids, expected_returned_counts,
+ arraysize(expected_returned_counts), consumer_sync_token_);
+ }
+}
+
+TEST_F(CompositorFrameSinkSupportTest, AddDuringEviction) {
+ cc::test::MockCompositorFrameSinkSupportClient mock_client;
+ auto support = CompositorFrameSinkSupport::Create(
+ &mock_client, &manager_, kAnotherArbitraryFrameSinkId, kIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_surface_id(6, kArbitraryToken);
+ support->SubmitCompositorFrame(local_surface_id,
+ cc::test::MakeCompositorFrame());
+
+ EXPECT_CALL(mock_client, DidReceiveCompositorFrameAck(_))
+ .WillOnce(testing::InvokeWithoutArgs([&support, &mock_client]() {
+ LocalSurfaceId new_id(7, base::UnguessableToken::Create());
+ support->SubmitCompositorFrame(new_id, cc::test::MakeCompositorFrame());
+ }))
+ .WillRepeatedly(testing::Return());
+ support->EvictCurrentSurface();
+}
+
+// Tests doing an EvictCurrentSurface before shutting down the factory.
+TEST_F(CompositorFrameSinkSupportTest, EvictCurrentSurface) {
+ cc::test::MockCompositorFrameSinkSupportClient mock_client;
+ auto support = CompositorFrameSinkSupport::Create(
+ &mock_client, &manager_, kAnotherArbitraryFrameSinkId, kIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_surface_id(7, kArbitraryToken);
+ SurfaceId id(kAnotherArbitraryFrameSinkId, local_surface_id);
+
+ cc::TransferableResource resource;
+ resource.id = 1;
+ resource.mailbox_holder.texture_target = GL_TEXTURE_2D;
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.resource_list.push_back(resource);
+ support->SubmitCompositorFrame(local_surface_id, std::move(frame));
+ EXPECT_EQ(surface_observer_.last_created_surface_id().local_surface_id(),
+ local_surface_id);
+ local_surface_id_ = LocalSurfaceId();
+
+ std::vector<cc::ReturnedResource> returned_resources = {
+ resource.ToReturnedResource()};
+ EXPECT_TRUE(GetSurfaceForId(id));
+ EXPECT_CALL(mock_client, DidReceiveCompositorFrameAck(returned_resources))
+ .Times(1);
+ support->EvictCurrentSurface();
+ EXPECT_FALSE(GetSurfaceForId(id));
+}
+
+// Tests doing an EvictCurrentSurface which has unregistered dependency.
+TEST_F(CompositorFrameSinkSupportTest,
+ EvictCurrentSurfaceDependencyUnRegistered) {
+ cc::test::MockCompositorFrameSinkSupportClient mock_client;
+ auto support = CompositorFrameSinkSupport::Create(
+ &mock_client, &manager_, kAnotherArbitraryFrameSinkId, kIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_surface_id(7, kArbitraryToken);
+
+ cc::TransferableResource resource;
+ resource.id = 1;
+ resource.mailbox_holder.texture_target = GL_TEXTURE_2D;
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.resource_list.push_back(resource);
+ support->SubmitCompositorFrame(local_surface_id, std::move(frame));
+ EXPECT_EQ(surface_observer_.last_created_surface_id().local_surface_id(),
+ local_surface_id);
+ local_surface_id_ = LocalSurfaceId();
+
+ SurfaceId surface_id(kAnotherArbitraryFrameSinkId, local_surface_id);
+ cc::Surface* surface = GetSurfaceForId(surface_id);
+ surface->AddDestructionDependency(
+ SurfaceSequence(kYetAnotherArbitraryFrameSinkId, 4));
+
+ std::vector<cc::ReturnedResource> returned_resource = {
+ resource.ToReturnedResource()};
+
+ EXPECT_TRUE(GetSurfaceForId(surface_id));
+ EXPECT_CALL(mock_client, DidReceiveCompositorFrameAck(returned_resource))
+ .Times(1);
+ support->EvictCurrentSurface();
+ EXPECT_FALSE(GetSurfaceForId(surface_id));
+}
+
+// Tests doing an EvictCurrentSurface which has registered dependency.
+TEST_F(CompositorFrameSinkSupportTest,
+ EvictCurrentSurfaceDependencyRegistered) {
+ cc::test::MockCompositorFrameSinkSupportClient mock_client;
+ auto support = CompositorFrameSinkSupport::Create(
+ &mock_client, &manager_, kAnotherArbitraryFrameSinkId, kIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ LocalSurfaceId local_surface_id(7, kArbitraryToken);
+
+ cc::TransferableResource resource;
+ resource.id = 1;
+ resource.mailbox_holder.texture_target = GL_TEXTURE_2D;
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.resource_list.push_back(resource);
+ uint32_t execute_count = 0;
+ support->SubmitCompositorFrame(local_surface_id, std::move(frame));
+ EXPECT_EQ(surface_observer_.last_created_surface_id().local_surface_id(),
+ local_surface_id);
+ local_surface_id_ = LocalSurfaceId();
+
+ manager_.surface_manager()->RegisterFrameSinkId(
+ kYetAnotherArbitraryFrameSinkId);
+
+ SurfaceId surface_id(kAnotherArbitraryFrameSinkId, local_surface_id);
+ cc::Surface* surface = GetSurfaceForId(surface_id);
+ surface->AddDestructionDependency(
+ SurfaceSequence(kYetAnotherArbitraryFrameSinkId, 4));
+
+ std::vector<cc::ReturnedResource> returned_resources;
+ EXPECT_TRUE(GetSurfaceForId(surface_id));
+ support->EvictCurrentSurface();
+ EXPECT_TRUE(GetSurfaceForId(surface_id));
+ EXPECT_EQ(0u, execute_count);
+
+ returned_resources.push_back(resource.ToReturnedResource());
+ EXPECT_CALL(mock_client, DidReceiveCompositorFrameAck(returned_resources))
+ .Times(1);
+ manager_.surface_manager()->SatisfySequence(
+ SurfaceSequence(kYetAnotherArbitraryFrameSinkId, 4));
+ EXPECT_FALSE(GetSurfaceForId(surface_id));
+}
+
+TEST_F(CompositorFrameSinkSupportTest, DestroySequence) {
+ LocalSurfaceId local_surface_id2(5, kArbitraryToken);
+ auto support2 = CompositorFrameSinkSupport::Create(
+ &fake_support_client_, &manager_, kYetAnotherArbitraryFrameSinkId,
+ kIsChildRoot, kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ SurfaceId id2(kYetAnotherArbitraryFrameSinkId, local_surface_id2);
+ support2->SubmitCompositorFrame(local_surface_id2,
+ cc::test::MakeCompositorFrame());
+
+ // Check that waiting before the sequence is satisfied works.
+ GetSurfaceForId(id2)->AddDestructionDependency(
+ SurfaceSequence(kYetAnotherArbitraryFrameSinkId, 4));
+ support2->EvictCurrentSurface();
+
+ DCHECK(GetSurfaceForId(id2));
+ manager_.surface_manager()->SatisfySequence(
+ SurfaceSequence(kYetAnotherArbitraryFrameSinkId, 4));
+ manager_.surface_manager()->SatisfySequence(
+ SurfaceSequence(kYetAnotherArbitraryFrameSinkId, 6));
+ DCHECK(!GetSurfaceForId(id2));
+
+ // Check that waiting after the sequence is satisfied works.
+ support2->SubmitCompositorFrame(local_surface_id2,
+ cc::test::MakeCompositorFrame());
+ DCHECK(GetSurfaceForId(id2));
+ GetSurfaceForId(id2)->AddDestructionDependency(
+ SurfaceSequence(kAnotherArbitraryFrameSinkId, 6));
+ support2->EvictCurrentSurface();
+ DCHECK(!GetSurfaceForId(id2));
+}
+
+// Tests that SurfaceId namespace invalidation correctly allows
+// Sequences to be ignored.
+TEST_F(CompositorFrameSinkSupportTest, InvalidFrameSinkId) {
+ FrameSinkId frame_sink_id(1234, 5678);
+
+ LocalSurfaceId local_surface_id(5, kArbitraryToken);
+ SurfaceId id(support_->frame_sink_id(), local_surface_id);
+ support_->SubmitCompositorFrame(local_surface_id,
+ cc::test::MakeCompositorFrame());
+
+ manager_.surface_manager()->RegisterFrameSinkId(frame_sink_id);
+ GetSurfaceForId(id)->AddDestructionDependency(
+ SurfaceSequence(frame_sink_id, 4));
+
+ support_->EvictCurrentSurface();
+
+ // Verify the dependency has prevented the surface from getting destroyed.
+ EXPECT_TRUE(GetSurfaceForId(id));
+
+ manager_.surface_manager()->InvalidateFrameSinkId(frame_sink_id);
+
+ // Verify that the invalidated namespace caused the unsatisfied sequence
+ // to be ignored.
+ EXPECT_FALSE(GetSurfaceForId(id));
+}
+
+TEST_F(CompositorFrameSinkSupportTest, DestroyCycle) {
+ LocalSurfaceId local_surface_id2(5, kArbitraryToken);
+ SurfaceId id2(kYetAnotherArbitraryFrameSinkId, local_surface_id2);
+ auto support2 = CompositorFrameSinkSupport::Create(
+ &fake_support_client_, &manager_, kYetAnotherArbitraryFrameSinkId,
+ kIsChildRoot, kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints);
+ manager_.surface_manager()->RegisterFrameSinkId(kAnotherArbitraryFrameSinkId);
+ // Give local_surface_id_ an initial frame so another client can refer to
+ // that surface.
+ {
+ auto frame = cc::test::MakeCompositorFrame();
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame));
+ }
+ // Give id2 a frame that references local_surface_id_.
+ {
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.metadata.referenced_surfaces.push_back(
+ SurfaceId(support_->frame_sink_id(), local_surface_id_));
+ support2->SubmitCompositorFrame(local_surface_id2, std::move(frame));
+ EXPECT_EQ(surface_observer_.last_created_surface_id().local_surface_id(),
+ local_surface_id2);
+ }
+ GetSurfaceForId(id2)->AddDestructionDependency(
+ SurfaceSequence(kAnotherArbitraryFrameSinkId, 4));
+ support2->EvictCurrentSurface();
+ // Give local_surface_id_ a frame that references id2.
+ {
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.metadata.referenced_surfaces.push_back(id2);
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame));
+ }
+ support_->EvictCurrentSurface();
+ EXPECT_TRUE(GetSurfaceForId(id2));
+ // local_surface_id_ should be retained by reference from id2.
+ EXPECT_TRUE(
+ GetSurfaceForId(SurfaceId(support_->frame_sink_id(), local_surface_id_)));
+
+ // Satisfy last destruction dependency for id2.
+ manager_.surface_manager()->SatisfySequence(
+ SurfaceSequence(kAnotherArbitraryFrameSinkId, 4));
+
+ // id2 and local_surface_id_ are in a reference cycle that has no surface
+ // sequences holding on to it, so they should be destroyed.
+ EXPECT_TRUE(!GetSurfaceForId(id2));
+ EXPECT_TRUE(!GetSurfaceForId(
+ SurfaceId(support_->frame_sink_id(), local_surface_id_)));
+
+ local_surface_id_ = LocalSurfaceId();
+}
+
+void CopyRequestTestCallback(bool* called,
+ std::unique_ptr<cc::CopyOutputResult> result) {
+ *called = true;
+}
+
+TEST_F(CompositorFrameSinkSupportTest, DuplicateCopyRequest) {
+ {
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.metadata.referenced_surfaces.push_back(
+ SurfaceId(support_->frame_sink_id(), local_surface_id_));
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame));
+ EXPECT_EQ(surface_observer_.last_created_surface_id().local_surface_id(),
+ local_surface_id_);
+ }
+
+ bool called1 = false;
+ auto request = cc::CopyOutputRequest::CreateRequest(
+ base::BindOnce(&CopyRequestTestCallback, &called1));
+ request->set_source(kArbitrarySourceId1);
+
+ support_->RequestCopyOfSurface(std::move(request));
+ EXPECT_FALSE(called1);
+
+ bool called2 = false;
+ request = cc::CopyOutputRequest::CreateRequest(
+ base::BindOnce(&CopyRequestTestCallback, &called2));
+ request->set_source(kArbitrarySourceId2);
+
+ support_->RequestCopyOfSurface(std::move(request));
+ // Callbacks have different sources so neither should be called.
+ EXPECT_FALSE(called1);
+ EXPECT_FALSE(called2);
+
+ bool called3 = false;
+ request = cc::CopyOutputRequest::CreateRequest(
+ base::BindOnce(&CopyRequestTestCallback, &called3));
+ request->set_source(kArbitrarySourceId1);
+
+ support_->RequestCopyOfSurface(std::move(request));
+ // Two callbacks are from source1, so the first should be called.
+ EXPECT_TRUE(called1);
+ EXPECT_FALSE(called2);
+ EXPECT_FALSE(called3);
+
+ support_->EvictCurrentSurface();
+ local_surface_id_ = LocalSurfaceId();
+ EXPECT_TRUE(called1);
+ EXPECT_TRUE(called2);
+ EXPECT_TRUE(called3);
+}
+
+// Check whether the SurfaceInfo object is created and populated correctly
+// after the frame submission.
+TEST_F(CompositorFrameSinkSupportTest, SurfaceInfo) {
+ auto frame = cc::test::MakeCompositorFrame();
+
+ auto render_pass = cc::RenderPass::Create();
+ render_pass->SetNew(1, gfx::Rect(5, 6), gfx::Rect(), gfx::Transform());
+ frame.render_pass_list.push_back(std::move(render_pass));
+
+ render_pass = cc::RenderPass::Create();
+ render_pass->SetNew(2, gfx::Rect(7, 8), gfx::Rect(), gfx::Transform());
+ frame.render_pass_list.push_back(std::move(render_pass));
+
+ frame.metadata.device_scale_factor = 2.5f;
+
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame));
+ SurfaceId expected_surface_id(support_->frame_sink_id(), local_surface_id_);
+ EXPECT_EQ(expected_surface_id, surface_observer_.last_surface_info().id());
+ EXPECT_EQ(2.5f, surface_observer_.last_surface_info().device_scale_factor());
+ EXPECT_EQ(gfx::Size(7, 8),
+ surface_observer_.last_surface_info().size_in_pixels());
+}
+
+// Check that if a CompositorFrame is received with size zero, we don't create
+// a Surface for it.
+TEST_F(CompositorFrameSinkSupportTest, ZeroFrameSize) {
+ SurfaceId id(support_->frame_sink_id(), local_surface_id_);
+ auto frame = cc::test::MakeEmptyCompositorFrame();
+ frame.render_pass_list.push_back(cc::RenderPass::Create());
+ EXPECT_TRUE(
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame)));
+ EXPECT_FALSE(GetSurfaceForId(id));
+}
+
+// Check that if a CompositorFrame is received with device scale factor of 0, we
+// don't create a Surface for it.
+TEST_F(CompositorFrameSinkSupportTest, ZeroDeviceScaleFactor) {
+ SurfaceId id(support_->frame_sink_id(), local_surface_id_);
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.metadata.device_scale_factor = 0.f;
+ EXPECT_TRUE(
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame)));
+ EXPECT_FALSE(GetSurfaceForId(id));
+}
+
+// Check that if the size of a CompositorFrame doesn't match the size of the
+// Surface it's being submitted to, we skip the frame.
+TEST_F(CompositorFrameSinkSupportTest, FrameSizeMismatch) {
+ SurfaceId id(support_->frame_sink_id(), local_surface_id_);
+
+ // Submit a frame with size (5,5).
+ auto frame = cc::test::MakeEmptyCompositorFrame();
+ auto pass = cc::RenderPass::Create();
+ pass->SetNew(1, gfx::Rect(5, 5), gfx::Rect(), gfx::Transform());
+ frame.render_pass_list.push_back(std::move(pass));
+ EXPECT_TRUE(
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame)));
+ EXPECT_TRUE(GetSurfaceForId(id));
+
+ // Submit a frame with size (5,4). This frame should be rejected and the
+ // surface should be destroyed.
+ frame = cc::test::MakeEmptyCompositorFrame();
+ pass = cc::RenderPass::Create();
+ pass->SetNew(1, gfx::Rect(5, 4), gfx::Rect(), gfx::Transform());
+ frame.render_pass_list.push_back(std::move(pass));
+ EXPECT_FALSE(
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame)));
+ EXPECT_FALSE(GetSurfaceForId(id));
+}
+
+// Check that if the device scale factor of a CompositorFrame doesn't match the
+// device scale factor of the Surface it's being submitted to, the frame is
+// rejected and the surface is destroyed.
+TEST_F(CompositorFrameSinkSupportTest, DeviceScaleFactorMismatch) {
+ SurfaceId id(support_->frame_sink_id(), local_surface_id_);
+
+ // Submit a frame with device scale factor of 0.5.
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.metadata.device_scale_factor = 0.5f;
+ EXPECT_TRUE(
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame)));
+ EXPECT_TRUE(GetSurfaceForId(id));
+
+ // Submit a frame with device scale factor of 0.4. This frame should be
+ // rejected and the surface should be destroyed.
+ frame = cc::test::MakeCompositorFrame();
+ frame.metadata.device_scale_factor = 0.4f;
+ EXPECT_FALSE(
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame)));
+ EXPECT_FALSE(GetSurfaceForId(id));
+}
+
+TEST_F(CompositorFrameSinkSupportTest, PassesOnBeginFrameAcks) {
+ // Request BeginFrames.
+ support_->SetNeedsBeginFrame(true);
+
+ // Issue a BeginFrame.
+ cc::BeginFrameArgs args =
+ cc::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
+ begin_frame_source_.TestOnBeginFrame(args);
+
+ // Check that the support and SurfaceManager forward the BeginFrameAck
+ // attached to a CompositorFrame to the SurfaceObserver.
+ cc::BeginFrameAck ack(0, 1, true);
+ auto frame = cc::test::MakeCompositorFrame();
+ frame.metadata.begin_frame_ack = ack;
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame));
+ EXPECT_EQ(ack, surface_observer_.last_ack());
+
+ // Issue another BeginFrame.
+ args = cc::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2);
+ begin_frame_source_.TestOnBeginFrame(args);
+
+ // Check that the support and SurfaceManager forward a DidNotProduceFrame ack
+ // to the SurfaceObserver.
+ cc::BeginFrameAck ack2(0, 2, false);
+ support_->DidNotProduceFrame(ack2);
+ EXPECT_EQ(ack2, surface_observer_.last_ack());
+
+ support_->SetNeedsBeginFrame(false);
+}
+
+} // namespace
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc b/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc
new file mode 100644
index 00000000000..d0e94605b3f
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc
@@ -0,0 +1,173 @@
+// Copyright 2014 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/frame_sinks/direct_layer_tree_frame_sink.h"
+
+#include "base/bind.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/layer_tree_frame_sink_client.h"
+#include "cc/surfaces/surface.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/local_surface_id_allocator.h"
+#include "components/viz/service/display/display.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support_manager.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+
+namespace viz {
+
+DirectLayerTreeFrameSink::DirectLayerTreeFrameSink(
+ const FrameSinkId& frame_sink_id,
+ CompositorFrameSinkSupportManager* support_manager,
+ FrameSinkManagerImpl* frame_sink_manager,
+ Display* display,
+ scoped_refptr<ContextProvider> context_provider,
+ scoped_refptr<ContextProvider> worker_context_provider,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ SharedBitmapManager* shared_bitmap_manager)
+ : LayerTreeFrameSink(std::move(context_provider),
+ std::move(worker_context_provider),
+ gpu_memory_buffer_manager,
+ shared_bitmap_manager),
+ frame_sink_id_(frame_sink_id),
+ support_manager_(support_manager),
+ frame_sink_manager_(frame_sink_manager),
+ display_(display) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ capabilities_.must_always_swap = true;
+ // Display and DirectLayerTreeFrameSink share a GL context, so sync
+ // points aren't needed when passing resources between them.
+ capabilities_.delegated_sync_points_required = false;
+}
+
+DirectLayerTreeFrameSink::DirectLayerTreeFrameSink(
+ const FrameSinkId& frame_sink_id,
+ CompositorFrameSinkSupportManager* support_manager,
+ FrameSinkManagerImpl* frame_sink_manager,
+ Display* display,
+ scoped_refptr<cc::VulkanContextProvider> vulkan_context_provider)
+ : LayerTreeFrameSink(std::move(vulkan_context_provider)),
+ frame_sink_id_(frame_sink_id),
+ support_manager_(support_manager),
+ frame_sink_manager_(frame_sink_manager),
+ display_(display) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ capabilities_.must_always_swap = true;
+}
+
+DirectLayerTreeFrameSink::~DirectLayerTreeFrameSink() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+bool DirectLayerTreeFrameSink::BindToClient(
+ cc::LayerTreeFrameSinkClient* client) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ if (!cc::LayerTreeFrameSink::BindToClient(client))
+ return false;
+
+ // We want the Display's output surface to hear about lost context, and since
+ // this shares a context with it, we should not be listening for lost context
+ // callbacks on the context here.
+ if (auto* cp = context_provider())
+ cp->SetLostContextCallback(base::Closure());
+
+ constexpr bool is_root = true;
+ constexpr bool handles_frame_sink_id_invalidation = false;
+ support_ = support_manager_->CreateCompositorFrameSinkSupport(
+ this, frame_sink_id_, is_root, handles_frame_sink_id_invalidation,
+ capabilities_.delegated_sync_points_required);
+ begin_frame_source_ = base::MakeUnique<cc::ExternalBeginFrameSource>(this);
+ client_->SetBeginFrameSource(begin_frame_source_.get());
+
+ // Avoid initializing GL context here, as this should be sharing the
+ // Display's context.
+ display_->Initialize(this, frame_sink_manager_->surface_manager());
+ return true;
+}
+
+void DirectLayerTreeFrameSink::DetachFromClient() {
+ client_->SetBeginFrameSource(nullptr);
+ begin_frame_source_.reset();
+
+ // Unregister the SurfaceFactoryClient here instead of the dtor so that only
+ // one client is alive for this namespace at any given time.
+ support_.reset();
+
+ cc::LayerTreeFrameSink::DetachFromClient();
+}
+
+void DirectLayerTreeFrameSink::SubmitCompositorFrame(
+ cc::CompositorFrame frame) {
+ DCHECK(frame.metadata.begin_frame_ack.has_damage);
+ DCHECK_LE(cc::BeginFrameArgs::kStartingFrameNumber,
+ frame.metadata.begin_frame_ack.sequence_number);
+
+ gfx::Size frame_size = frame.render_pass_list.back()->output_rect.size();
+ if (!local_surface_id_.is_valid() || frame_size != last_swap_frame_size_ ||
+ frame.metadata.device_scale_factor != device_scale_factor_) {
+ local_surface_id_ = local_surface_id_allocator_.GenerateId();
+ last_swap_frame_size_ = frame_size;
+ device_scale_factor_ = frame.metadata.device_scale_factor;
+ display_->SetLocalSurfaceId(local_surface_id_, device_scale_factor_);
+ }
+
+ bool result =
+ support_->SubmitCompositorFrame(local_surface_id_, std::move(frame));
+ DCHECK(result);
+}
+
+void DirectLayerTreeFrameSink::DidNotProduceFrame(
+ const cc::BeginFrameAck& ack) {
+ DCHECK(!ack.has_damage);
+ DCHECK_LE(cc::BeginFrameArgs::kStartingFrameNumber, ack.sequence_number);
+ support_->DidNotProduceFrame(ack);
+}
+
+void DirectLayerTreeFrameSink::DisplayOutputSurfaceLost() {
+ is_lost_ = true;
+ client_->DidLoseLayerTreeFrameSink();
+}
+
+void DirectLayerTreeFrameSink::DisplayWillDrawAndSwap(
+ bool will_draw_and_swap,
+ const cc::RenderPassList& render_passes) {
+ // This notification is not relevant to our client outside of tests.
+}
+
+void DirectLayerTreeFrameSink::DisplayDidDrawAndSwap() {
+ // This notification is not relevant to our client outside of tests. We
+ // unblock the client from DidDrawCallback() when the surface is going to
+ // be drawn.
+}
+
+void DirectLayerTreeFrameSink::DidReceiveCompositorFrameAck(
+ const std::vector<cc::ReturnedResource>& resources) {
+ client_->ReclaimResources(resources);
+ client_->DidReceiveCompositorFrameAck();
+}
+
+void DirectLayerTreeFrameSink::OnBeginFrame(const cc::BeginFrameArgs& args) {
+ begin_frame_source_->OnBeginFrame(args);
+}
+
+void DirectLayerTreeFrameSink::ReclaimResources(
+ const std::vector<cc::ReturnedResource>& resources) {
+ client_->ReclaimResources(resources);
+}
+
+void DirectLayerTreeFrameSink::WillDrawSurface(
+ const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) {
+ // TODO(staraz): Implement this.
+}
+
+void DirectLayerTreeFrameSink::OnBeginFramePausedChanged(bool paused) {
+ begin_frame_source_->OnSetBeginFrameSourcePaused(paused);
+}
+
+void DirectLayerTreeFrameSink::OnNeedsBeginFrames(bool needs_begin_frame) {
+ support_->SetNeedsBeginFrame(needs_begin_frame);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h b/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h
new file mode 100644
index 00000000000..0a1724f2bf1
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h
@@ -0,0 +1,102 @@
+// Copyright 2014 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_FRAME_SINKS_DIRECT_LAYER_TREE_FRAME_SINK_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_DIRECT_LAYER_TREE_FRAME_SINK_H_
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "cc/output/layer_tree_frame_sink.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "components/viz/common/surfaces/local_surface_id_allocator.h"
+#include "components/viz/service/display/display_client.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support_client.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace cc {
+class LocalSurfaceIdAllocator;
+class FrameSinkManagerImpl;
+} // namespace cc
+
+namespace viz {
+class CompositorFrameSinkSupportManager;
+class Display;
+
+// This class submits compositor frames to an in-process Display, with the
+// client's frame being the root surface of the Display.
+class VIZ_SERVICE_EXPORT DirectLayerTreeFrameSink
+ : public cc::LayerTreeFrameSink,
+ public NON_EXPORTED_BASE(CompositorFrameSinkSupportClient),
+ public cc::ExternalBeginFrameSourceClient,
+ public NON_EXPORTED_BASE(DisplayClient) {
+ public:
+ // The underlying Display, FrameSinkManagerImpl, and LocalSurfaceIdAllocator
+ // must outlive this class.
+ DirectLayerTreeFrameSink(
+ const FrameSinkId& frame_sink_id,
+ CompositorFrameSinkSupportManager* support_manager,
+ FrameSinkManagerImpl* frame_sink_manager,
+ Display* display,
+ scoped_refptr<ContextProvider> context_provider,
+ scoped_refptr<ContextProvider> worker_context_provider,
+ gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
+ SharedBitmapManager* shared_bitmap_manager);
+ DirectLayerTreeFrameSink(
+ const FrameSinkId& frame_sink_id,
+ CompositorFrameSinkSupportManager* support_manager,
+ FrameSinkManagerImpl* frame_sink_manager,
+ Display* display,
+ scoped_refptr<cc::VulkanContextProvider> vulkan_context_provider);
+ ~DirectLayerTreeFrameSink() override;
+
+ // LayerTreeFrameSink implementation.
+ bool BindToClient(cc::LayerTreeFrameSinkClient* client) override;
+ void DetachFromClient() override;
+ void SubmitCompositorFrame(cc::CompositorFrame frame) override;
+ void DidNotProduceFrame(const cc::BeginFrameAck& ack) override;
+
+ // DisplayClient implementation.
+ void DisplayOutputSurfaceLost() override;
+ void DisplayWillDrawAndSwap(bool will_draw_and_swap,
+ const cc::RenderPassList& render_passes) override;
+ void DisplayDidDrawAndSwap() override;
+
+ protected:
+ std::unique_ptr<CompositorFrameSinkSupport> support_; // protected for test.
+
+ private:
+ // CompositorFrameSinkSupportClient implementation:
+ void DidReceiveCompositorFrameAck(
+ const std::vector<cc::ReturnedResource>& resources) override;
+ void OnBeginFrame(const cc::BeginFrameArgs& args) override;
+ void ReclaimResources(
+ const std::vector<cc::ReturnedResource>& resources) override;
+ void WillDrawSurface(const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) override;
+ void OnBeginFramePausedChanged(bool paused) override;
+
+ // ExternalBeginFrameSourceClient implementation:
+ void OnNeedsBeginFrames(bool needs_begin_frame) override;
+
+ // This class is only meant to be used on a single thread.
+ THREAD_CHECKER(thread_checker_);
+
+ const FrameSinkId frame_sink_id_;
+ LocalSurfaceId local_surface_id_;
+ CompositorFrameSinkSupportManager* const support_manager_;
+ FrameSinkManagerImpl* frame_sink_manager_;
+ LocalSurfaceIdAllocator local_surface_id_allocator_;
+ Display* display_;
+ gfx::Size last_swap_frame_size_;
+ float device_scale_factor_ = 1.f;
+ bool is_lost_ = false;
+ std::unique_ptr<cc::ExternalBeginFrameSource> begin_frame_source_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectLayerTreeFrameSink);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_DIRECT_LAYER_TREE_FRAME_SINK_H_
diff --git a/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink_unittest.cc b/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink_unittest.cc
new file mode 100644
index 00000000000..3b050b4c65d
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/direct_layer_tree_frame_sink_unittest.cc
@@ -0,0 +1,177 @@
+// 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 "components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h"
+
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "cc/output/texture_mailbox_deleter.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/scheduler/delay_based_time_source.h"
+#include "cc/test/begin_frame_args_test.h"
+#include "cc/test/compositor_frame_helpers.h"
+#include "cc/test/fake_layer_tree_frame_sink_client.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/ordered_simple_task_runner.h"
+#include "cc/test/test_context_provider.h"
+#include "cc/test/test_gpu_memory_buffer_manager.h"
+#include "cc/test/test_shared_bitmap_manager.h"
+#include "components/viz/common/display/renderer_settings.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/local_surface_id_allocator.h"
+#include "components/viz/service/display/display.h"
+#include "components/viz/service/display/display_scheduler.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support_manager.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace viz {
+namespace {
+
+static constexpr FrameSinkId kArbitraryFrameSinkId(1, 1);
+
+class TestDirectLayerTreeFrameSink : public DirectLayerTreeFrameSink {
+ public:
+ using DirectLayerTreeFrameSink::DirectLayerTreeFrameSink;
+
+ CompositorFrameSinkSupport* support() const { return support_.get(); }
+};
+
+class TestCompositorFrameSinkSupportManager
+ : public CompositorFrameSinkSupportManager {
+ public:
+ explicit TestCompositorFrameSinkSupportManager(
+ FrameSinkManagerImpl* frame_sink_manager)
+ : frame_sink_manager_(frame_sink_manager) {}
+ ~TestCompositorFrameSinkSupportManager() override = default;
+
+ std::unique_ptr<CompositorFrameSinkSupport> CreateCompositorFrameSinkSupport(
+ CompositorFrameSinkSupportClient* client,
+ const FrameSinkId& frame_sink_id,
+ bool is_root,
+ bool handles_frame_sink_id_invalidation,
+ bool needs_sync_points) override {
+ return CompositorFrameSinkSupport::Create(
+ client, frame_sink_manager_, frame_sink_id, is_root,
+ handles_frame_sink_id_invalidation, needs_sync_points);
+ }
+
+ private:
+ FrameSinkManagerImpl* const frame_sink_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCompositorFrameSinkSupportManager);
+};
+
+class DirectLayerTreeFrameSinkTest : public testing::Test {
+ public:
+ DirectLayerTreeFrameSinkTest()
+ : now_src_(new base::SimpleTestTickClock()),
+ task_runner_(new cc::OrderedSimpleTaskRunner(now_src_.get(), true)),
+ display_size_(1920, 1080),
+ display_rect_(display_size_),
+ support_manager_(&frame_sink_manager_),
+ context_provider_(cc::TestContextProvider::Create()) {
+ auto display_output_surface = cc::FakeOutputSurface::Create3d();
+ display_output_surface_ = display_output_surface.get();
+
+ begin_frame_source_ = base::MakeUnique<cc::BackToBackBeginFrameSource>(
+ base::MakeUnique<cc::DelayBasedTimeSource>(task_runner_.get()));
+
+ int max_frames_pending = 2;
+ std::unique_ptr<DisplayScheduler> scheduler(new DisplayScheduler(
+ begin_frame_source_.get(), task_runner_.get(), max_frames_pending));
+
+ display_.reset(new Display(
+ &bitmap_manager_, &gpu_memory_buffer_manager_, RendererSettings(),
+ kArbitraryFrameSinkId, std::move(display_output_surface),
+ std::move(scheduler),
+ base::MakeUnique<cc::TextureMailboxDeleter>(task_runner_.get())));
+ layer_tree_frame_sink_ = base::MakeUnique<TestDirectLayerTreeFrameSink>(
+ kArbitraryFrameSinkId, &support_manager_, &frame_sink_manager_,
+ display_.get(), context_provider_, nullptr, &gpu_memory_buffer_manager_,
+ &bitmap_manager_);
+
+ layer_tree_frame_sink_->BindToClient(&layer_tree_frame_sink_client_);
+ display_->Resize(display_size_);
+ display_->SetVisible(true);
+
+ EXPECT_FALSE(
+ layer_tree_frame_sink_client_.did_lose_layer_tree_frame_sink_called());
+ }
+
+ ~DirectLayerTreeFrameSinkTest() override {
+ layer_tree_frame_sink_->DetachFromClient();
+ }
+
+ void SwapBuffersWithDamage(const gfx::Rect& damage_rect) {
+ auto render_pass = cc::RenderPass::Create();
+ render_pass->SetNew(1, display_rect_, damage_rect, gfx::Transform());
+
+ cc::CompositorFrame frame = cc::test::MakeEmptyCompositorFrame();
+ frame.metadata.begin_frame_ack = cc::BeginFrameAck(0, 1, true);
+ frame.render_pass_list.push_back(std::move(render_pass));
+
+ layer_tree_frame_sink_->SubmitCompositorFrame(std::move(frame));
+ }
+
+ void SetUp() override {
+ // Draw the first frame to start in an "unlocked" state.
+ SwapBuffersWithDamage(display_rect_);
+
+ EXPECT_EQ(0u, display_output_surface_->num_sent_frames());
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
+ }
+
+ protected:
+ std::unique_ptr<base::SimpleTestTickClock> now_src_;
+ scoped_refptr<cc::OrderedSimpleTaskRunner> task_runner_;
+
+ const gfx::Size display_size_;
+ const gfx::Rect display_rect_;
+ FrameSinkManagerImpl frame_sink_manager_;
+ TestCompositorFrameSinkSupportManager support_manager_;
+ cc::TestSharedBitmapManager bitmap_manager_;
+ cc::TestGpuMemoryBufferManager gpu_memory_buffer_manager_;
+
+ scoped_refptr<cc::TestContextProvider> context_provider_;
+ cc::FakeOutputSurface* display_output_surface_ = nullptr;
+ std::unique_ptr<cc::BackToBackBeginFrameSource> begin_frame_source_;
+ std::unique_ptr<Display> display_;
+ cc::FakeLayerTreeFrameSinkClient layer_tree_frame_sink_client_;
+ std::unique_ptr<TestDirectLayerTreeFrameSink> layer_tree_frame_sink_;
+};
+
+TEST_F(DirectLayerTreeFrameSinkTest, DamageTriggersSwapBuffers) {
+ SwapBuffersWithDamage(display_rect_);
+ EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(2u, display_output_surface_->num_sent_frames());
+}
+
+TEST_F(DirectLayerTreeFrameSinkTest, NoDamageDoesNotTriggerSwapBuffers) {
+ SwapBuffersWithDamage(gfx::Rect());
+ EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
+}
+
+TEST_F(DirectLayerTreeFrameSinkTest, SuspendedDoesNotTriggerSwapBuffers) {
+ SwapBuffersWithDamage(display_rect_);
+ EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
+ display_output_surface_->set_suspended_for_recycle(true);
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
+ SwapBuffersWithDamage(display_rect_);
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
+ display_output_surface_->set_suspended_for_recycle(false);
+ SwapBuffersWithDamage(display_rect_);
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(2u, display_output_surface_->num_sent_frames());
+}
+
+} // namespace
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/frame_eviction_manager.cc b/chromium/components/viz/service/frame_sinks/frame_eviction_manager.cc
new file mode 100644
index 00000000000..d6ed3980042
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/frame_eviction_manager.cc
@@ -0,0 +1,179 @@
+// 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/frame_sinks/frame_eviction_manager.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/memory_coordinator_client_registry.h"
+#include "base/memory/memory_coordinator_proxy.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/memory/memory_pressure_monitor.h"
+#include "base/memory/shared_memory.h"
+#include "base/sys_info.h"
+#include "build/build_config.h"
+#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
+
+namespace viz {
+namespace {
+
+const int kModeratePressurePercentage = 50;
+const int kCriticalPressurePercentage = 10;
+
+} // namespace
+
+FrameEvictionManager* FrameEvictionManager::GetInstance() {
+ return base::Singleton<FrameEvictionManager>::get();
+}
+
+void FrameEvictionManager::AddFrame(FrameEvictionManagerClient* frame,
+ bool locked) {
+ RemoveFrame(frame);
+ if (locked)
+ locked_frames_[frame] = 1;
+ else
+ unlocked_frames_.push_front(frame);
+ CullUnlockedFrames(GetMaxNumberOfSavedFrames());
+}
+
+void FrameEvictionManager::RemoveFrame(FrameEvictionManagerClient* frame) {
+ std::map<FrameEvictionManagerClient*, size_t>::iterator locked_iter =
+ locked_frames_.find(frame);
+ if (locked_iter != locked_frames_.end())
+ locked_frames_.erase(locked_iter);
+ unlocked_frames_.remove(frame);
+}
+
+void FrameEvictionManager::LockFrame(FrameEvictionManagerClient* frame) {
+ std::list<FrameEvictionManagerClient*>::iterator unlocked_iter =
+ std::find(unlocked_frames_.begin(), unlocked_frames_.end(), frame);
+ if (unlocked_iter != unlocked_frames_.end()) {
+ DCHECK(locked_frames_.find(frame) == locked_frames_.end());
+ unlocked_frames_.remove(frame);
+ locked_frames_[frame] = 1;
+ } else {
+ DCHECK(locked_frames_.find(frame) != locked_frames_.end());
+ locked_frames_[frame]++;
+ }
+}
+
+void FrameEvictionManager::UnlockFrame(FrameEvictionManagerClient* frame) {
+ DCHECK(locked_frames_.find(frame) != locked_frames_.end());
+ size_t locked_count = locked_frames_[frame];
+ DCHECK(locked_count);
+ if (locked_count > 1) {
+ locked_frames_[frame]--;
+ } else {
+ RemoveFrame(frame);
+ unlocked_frames_.push_front(frame);
+ CullUnlockedFrames(GetMaxNumberOfSavedFrames());
+ }
+}
+
+size_t FrameEvictionManager::GetMaxNumberOfSavedFrames() const {
+ int percentage = 100;
+ auto* memory_coordinator_proxy = base::MemoryCoordinatorProxy::GetInstance();
+ if (memory_coordinator_proxy) {
+ switch (memory_coordinator_proxy->GetCurrentMemoryState()) {
+ case base::MemoryState::NORMAL:
+ percentage = 100;
+ break;
+ case base::MemoryState::THROTTLED:
+ percentage = kCriticalPressurePercentage;
+ break;
+ case base::MemoryState::SUSPENDED:
+ case base::MemoryState::UNKNOWN:
+ NOTREACHED();
+ break;
+ }
+ } else {
+ base::MemoryPressureMonitor* monitor = base::MemoryPressureMonitor::Get();
+
+ if (!monitor)
+ return max_number_of_saved_frames_;
+
+ // Until we have a global OnMemoryPressureChanged event we need to query the
+ // value from our specific pressure monitor.
+ switch (monitor->GetCurrentPressureLevel()) {
+ case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
+ percentage = 100;
+ break;
+ case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
+ percentage = kModeratePressurePercentage;
+ break;
+ case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
+ percentage = kCriticalPressurePercentage;
+ break;
+ }
+ }
+ size_t frames = (max_number_of_saved_frames_ * percentage) / 100;
+ return std::max(static_cast<size_t>(1), frames);
+}
+
+FrameEvictionManager::FrameEvictionManager()
+ : memory_pressure_listener_(new base::MemoryPressureListener(
+ base::Bind(&FrameEvictionManager::OnMemoryPressure,
+ base::Unretained(this)))) {
+ base::MemoryCoordinatorClientRegistry::GetInstance()->Register(this);
+ max_number_of_saved_frames_ =
+#if defined(OS_ANDROID)
+ // If the amount of memory on the device is >= 3.5 GB, save up to 5
+ // frames.
+ base::SysInfo::AmountOfPhysicalMemoryMB() < 1024 * 3.5f ? 1 : 5;
+#else
+ std::min(5, 2 + (base::SysInfo::AmountOfPhysicalMemoryMB() / 256));
+#endif
+ max_handles_ = base::SharedMemory::GetHandleLimit() / 8.0f;
+}
+
+FrameEvictionManager::~FrameEvictionManager() {}
+
+void FrameEvictionManager::CullUnlockedFrames(size_t saved_frame_limit) {
+ if (unlocked_frames_.size() + locked_frames_.size() > 0) {
+ float handles_per_frame =
+ ServerSharedBitmapManager::current()->AllocatedBitmapCount() * 1.0f /
+ (unlocked_frames_.size() + locked_frames_.size());
+
+ saved_frame_limit = std::max(
+ 1, static_cast<int>(std::min(static_cast<float>(saved_frame_limit),
+ max_handles_ / handles_per_frame)));
+ }
+ while (!unlocked_frames_.empty() &&
+ unlocked_frames_.size() + locked_frames_.size() > saved_frame_limit) {
+ size_t old_size = unlocked_frames_.size();
+ // Should remove self from list.
+ unlocked_frames_.back()->EvictCurrentFrame();
+ DCHECK_EQ(unlocked_frames_.size() + 1, old_size);
+ }
+}
+
+void FrameEvictionManager::OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
+ switch (memory_pressure_level) {
+ case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
+ PurgeMemory(kModeratePressurePercentage);
+ break;
+ case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
+ PurgeMemory(kCriticalPressurePercentage);
+ break;
+ case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
+ // No need to change anything when there is no pressure.
+ return;
+ }
+}
+
+void FrameEvictionManager::OnPurgeMemory() {
+ PurgeMemory(kCriticalPressurePercentage);
+}
+
+void FrameEvictionManager::PurgeMemory(int percentage) {
+ int saved_frame_limit = max_number_of_saved_frames_;
+ if (saved_frame_limit <= 1)
+ return;
+ CullUnlockedFrames(std::max(1, (saved_frame_limit * percentage) / 100));
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/frame_eviction_manager.h b/chromium/components/viz/service/frame_sinks/frame_eviction_manager.h
new file mode 100644
index 00000000000..6e78277377b
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/frame_eviction_manager.h
@@ -0,0 +1,83 @@
+// 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_FRAME_SINKS_FRAME_EVICTION_MANAGER_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_EVICTION_MANAGER_H_
+
+#include <stddef.h>
+
+#include <list>
+#include <map>
+
+#include "base/macros.h"
+#include "base/memory/memory_coordinator_client.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/memory/singleton.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+class VIZ_SERVICE_EXPORT FrameEvictionManagerClient {
+ public:
+ virtual ~FrameEvictionManagerClient() {}
+ virtual void EvictCurrentFrame() = 0;
+};
+
+// This class is responsible for globally managing which renderers keep their
+// compositor frame when offscreen. We actively discard compositor frames for
+// offscreen tabs, but keep a minimum amount, as an LRU cache, to make switching
+// between a small set of tabs faster. The limit is a soft limit, because
+// clients can lock their frame to prevent it from being discarded, e.g. if the
+// tab is visible, or while capturing a screenshot.
+class VIZ_SERVICE_EXPORT FrameEvictionManager
+ : public base::MemoryCoordinatorClient {
+ public:
+ static FrameEvictionManager* GetInstance();
+
+ void AddFrame(FrameEvictionManagerClient*, bool locked);
+ void RemoveFrame(FrameEvictionManagerClient*);
+ void LockFrame(FrameEvictionManagerClient*);
+ void UnlockFrame(FrameEvictionManagerClient*);
+
+ size_t GetMaxNumberOfSavedFrames() const;
+
+ // For testing only
+ void set_max_number_of_saved_frames(size_t max_number_of_saved_frames) {
+ max_number_of_saved_frames_ = max_number_of_saved_frames;
+ }
+ void set_max_handles(float max_handles) { max_handles_ = max_handles; }
+
+ // React on memory pressure events to adjust the number of cached frames.
+ // Please make this private when crbug.com/443824 has been fixed.
+ void OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
+
+ private:
+ FrameEvictionManager();
+ ~FrameEvictionManager() override;
+
+ // base::MemoryCoordinatorClient implementation:
+ void OnPurgeMemory() override;
+
+ void CullUnlockedFrames(size_t saved_frame_limit);
+
+ void PurgeMemory(int percentage);
+
+ friend struct base::DefaultSingletonTraits<FrameEvictionManager>;
+
+ // Listens for system under pressure notifications and adjusts number of
+ // cached frames accordingly.
+ std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
+
+ std::map<FrameEvictionManagerClient*, size_t> locked_frames_;
+ std::list<FrameEvictionManagerClient*> unlocked_frames_;
+ size_t max_number_of_saved_frames_;
+ float max_handles_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameEvictionManager);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_EVICTION_MANAGER_H_
diff --git a/chromium/components/viz/service/frame_sinks/frame_evictor.cc b/chromium/components/viz/service/frame_sinks/frame_evictor.cc
new file mode 100644
index 00000000000..086f9fca51d
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/frame_evictor.cc
@@ -0,0 +1,56 @@
+// 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/frame_sinks/frame_evictor.h"
+
+#include "base/logging.h"
+
+namespace viz {
+
+FrameEvictor::FrameEvictor(FrameEvictorClient* client)
+ : client_(client), has_frame_(false), visible_(false) {}
+
+FrameEvictor::~FrameEvictor() {
+ DiscardedFrame();
+}
+
+void FrameEvictor::SwappedFrame(bool visible) {
+ visible_ = visible;
+ has_frame_ = true;
+ FrameEvictionManager::GetInstance()->AddFrame(this, visible);
+}
+
+void FrameEvictor::DiscardedFrame() {
+ FrameEvictionManager::GetInstance()->RemoveFrame(this);
+ has_frame_ = false;
+}
+
+void FrameEvictor::SetVisible(bool visible) {
+ if (visible_ == visible)
+ return;
+ visible_ = visible;
+ if (has_frame_) {
+ if (visible) {
+ LockFrame();
+ } else {
+ UnlockFrame();
+ }
+ }
+}
+
+void FrameEvictor::LockFrame() {
+ DCHECK(has_frame_);
+ FrameEvictionManager::GetInstance()->LockFrame(this);
+}
+
+void FrameEvictor::UnlockFrame() {
+ DCHECK(has_frame_);
+ FrameEvictionManager::GetInstance()->UnlockFrame(this);
+}
+
+void FrameEvictor::EvictCurrentFrame() {
+ client_->EvictDelegatedFrame();
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/frame_evictor.h b/chromium/components/viz/service/frame_sinks/frame_evictor.h
new file mode 100644
index 00000000000..be6ef5d5433
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/frame_evictor.h
@@ -0,0 +1,46 @@
+// 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_FRAME_SINKS_FRAME_EVICTOR_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_EVICTOR_H_
+
+#include "base/macros.h"
+#include "components/viz/service/frame_sinks/frame_eviction_manager.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+class VIZ_SERVICE_EXPORT FrameEvictorClient {
+ public:
+ virtual ~FrameEvictorClient() {}
+ virtual void EvictDelegatedFrame() = 0;
+};
+
+class VIZ_SERVICE_EXPORT FrameEvictor : public FrameEvictionManagerClient {
+ public:
+ // |client| must outlive |this|.
+ explicit FrameEvictor(FrameEvictorClient* client);
+ ~FrameEvictor() override;
+
+ void SwappedFrame(bool visible);
+ void DiscardedFrame();
+ void SetVisible(bool visible);
+ void LockFrame();
+ void UnlockFrame();
+ bool HasFrame() { return has_frame_; }
+
+ private:
+ // FrameEvictionManagerClient implementation.
+ void EvictCurrentFrame() override;
+
+ FrameEvictorClient* client_;
+ bool has_frame_;
+ bool visible_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameEvictor);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_EVICTOR_H_
diff --git a/chromium/components/viz/service/frame_sinks/frame_sink_manager_client.h b/chromium/components/viz/service/frame_sinks/frame_sink_manager_client.h
new file mode 100644
index 00000000000..da079e95496
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/frame_sink_manager_client.h
@@ -0,0 +1,27 @@
+// 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_FRAME_SINKS_FRAME_SINK_MANAGER_CLIENT_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_SINK_MANAGER_CLIENT_H_
+
+#include "components/viz/service/viz_service_export.h"
+
+namespace cc {
+class BeginFrameSource;
+}
+
+namespace viz {
+
+class VIZ_SERVICE_EXPORT FrameSinkManagerClient {
+ public:
+ virtual ~FrameSinkManagerClient() = default;
+
+ // This allows the FrameSinkManagerImpl to pass a BeginFrameSource to use.
+ virtual void SetBeginFrameSource(
+ cc::BeginFrameSource* begin_frame_source) = 0;
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_SINK_MANAGER_CLIENT_H_
diff --git a/chromium/components/viz/service/frame_sinks/frame_sink_manager_impl.cc b/chromium/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
new file mode 100644
index 00000000000..8fed8fb7c03
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
@@ -0,0 +1,353 @@
+// Copyright 2014 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/frame_sinks/frame_sink_manager_impl.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/logging.h"
+#include "components/viz/service/display/display.h"
+#include "components/viz/service/display_embedder/display_provider.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_client.h"
+#include "components/viz/service/frame_sinks/gpu_compositor_frame_sink.h"
+#include "components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.h"
+#include "components/viz/service/frame_sinks/primary_begin_frame_source.h"
+
+#if DCHECK_IS_ON()
+#include <sstream>
+#endif
+
+namespace viz {
+
+FrameSinkManagerImpl::FrameSinkSourceMapping::FrameSinkSourceMapping() =
+ default;
+
+FrameSinkManagerImpl::FrameSinkSourceMapping::FrameSinkSourceMapping(
+ const FrameSinkSourceMapping& other) = default;
+
+FrameSinkManagerImpl::FrameSinkSourceMapping::~FrameSinkSourceMapping() =
+ default;
+
+FrameSinkManagerImpl::FrameSinkManagerImpl(
+ DisplayProvider* display_provider,
+ cc::SurfaceManager::LifetimeType lifetime_type)
+ : display_provider_(display_provider),
+ surface_manager_(lifetime_type),
+ binding_(this) {
+ surface_manager_.AddObserver(this);
+}
+
+FrameSinkManagerImpl::~FrameSinkManagerImpl() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ // All FrameSinks should be unregistered prior to FrameSinkManager
+ // destruction.
+ compositor_frame_sinks_.clear();
+ DCHECK_EQ(clients_.size(), 0u);
+ DCHECK_EQ(registered_sources_.size(), 0u);
+ surface_manager_.RemoveObserver(this);
+}
+
+void FrameSinkManagerImpl::BindAndSetClient(
+ cc::mojom::FrameSinkManagerRequest request,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ cc::mojom::FrameSinkManagerClientPtr client) {
+ DCHECK(!client_);
+ DCHECK(!binding_.is_bound());
+ binding_.Bind(std::move(request), std::move(task_runner));
+ client_ptr_ = std::move(client);
+
+ client_ = client_ptr_.get();
+}
+
+void FrameSinkManagerImpl::SetLocalClient(
+ cc::mojom::FrameSinkManagerClient* client) {
+ DCHECK(!client_ptr_);
+
+ client_ = client;
+}
+
+void FrameSinkManagerImpl::CreateRootCompositorFrameSink(
+ const FrameSinkId& frame_sink_id,
+ gpu::SurfaceHandle surface_handle,
+ cc::mojom::CompositorFrameSinkAssociatedRequest request,
+ cc::mojom::CompositorFrameSinkPrivateRequest private_request,
+ cc::mojom::CompositorFrameSinkClientPtr client,
+ cc::mojom::DisplayPrivateAssociatedRequest display_private_request) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK_NE(surface_handle, gpu::kNullSurfaceHandle);
+ DCHECK_EQ(0u, compositor_frame_sinks_.count(frame_sink_id));
+ DCHECK(display_provider_);
+
+ std::unique_ptr<cc::BeginFrameSource> begin_frame_source;
+ auto display = display_provider_->CreateDisplay(frame_sink_id, surface_handle,
+ &begin_frame_source);
+
+ compositor_frame_sinks_[frame_sink_id] =
+ base::MakeUnique<GpuRootCompositorFrameSink>(
+ this, frame_sink_id, std::move(display),
+ std::move(begin_frame_source), std::move(request),
+ std::move(private_request), std::move(client),
+ std::move(display_private_request));
+}
+
+void FrameSinkManagerImpl::CreateCompositorFrameSink(
+ const FrameSinkId& frame_sink_id,
+ cc::mojom::CompositorFrameSinkRequest request,
+ cc::mojom::CompositorFrameSinkPrivateRequest private_request,
+ cc::mojom::CompositorFrameSinkClientPtr client) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK_EQ(0u, compositor_frame_sinks_.count(frame_sink_id));
+
+ compositor_frame_sinks_[frame_sink_id] =
+ base::MakeUnique<GpuCompositorFrameSink>(
+ this, frame_sink_id, std::move(request), std::move(private_request),
+ std::move(client));
+}
+
+void FrameSinkManagerImpl::RegisterFrameSinkHierarchy(
+ const FrameSinkId& parent_frame_sink_id,
+ const FrameSinkId& child_frame_sink_id) {
+ // If it's possible to reach the parent through the child's descendant chain,
+ // then this will create an infinite loop. Might as well just crash here.
+ CHECK(!ChildContains(child_frame_sink_id, parent_frame_sink_id));
+
+ std::vector<FrameSinkId>& children =
+ frame_sink_source_map_[parent_frame_sink_id].children;
+ for (size_t i = 0; i < children.size(); ++i)
+ DCHECK(children[i] != child_frame_sink_id);
+ children.push_back(child_frame_sink_id);
+
+ // If the parent has no source, then attaching it to this child will
+ // not change any downstream sources.
+ cc::BeginFrameSource* parent_source =
+ frame_sink_source_map_[parent_frame_sink_id].source;
+ if (!parent_source)
+ return;
+
+ DCHECK_EQ(registered_sources_.count(parent_source), 1u);
+ RecursivelyAttachBeginFrameSource(child_frame_sink_id, parent_source);
+}
+
+void FrameSinkManagerImpl::UnregisterFrameSinkHierarchy(
+ const FrameSinkId& parent_frame_sink_id,
+ const FrameSinkId& child_frame_sink_id) {
+ // Deliberately do not check validity of either parent or child
+ // FrameSinkId here. They were valid during the registration, so were
+ // valid at some point in time. This makes it possible to invalidate parent
+ // and child FrameSinkIds independently of each other and not have an ordering
+ // dependency of unregistering the hierarchy first before either of them.
+ DCHECK_EQ(frame_sink_source_map_.count(parent_frame_sink_id), 1u);
+
+ auto iter = frame_sink_source_map_.find(parent_frame_sink_id);
+
+ std::vector<FrameSinkId>& children = iter->second.children;
+ bool found_child = false;
+ for (size_t i = 0; i < children.size(); ++i) {
+ if (children[i] == child_frame_sink_id) {
+ found_child = true;
+ children[i] = children.back();
+ children.resize(children.size() - 1);
+ break;
+ }
+ }
+ DCHECK(found_child);
+
+ // The CompositorFrameSinkSupport and hierarchy can be registered/unregistered
+ // in either order, so empty frame_sink_source_map entries need to be
+ // checked when removing either clients or relationships.
+ if (!iter->second.has_children() && !clients_.count(parent_frame_sink_id) &&
+ !iter->second.source) {
+ frame_sink_source_map_.erase(iter);
+ return;
+ }
+
+ // If the parent does not have a begin frame source, then disconnecting it
+ // will not change any of its children.
+ cc::BeginFrameSource* parent_source = iter->second.source;
+ if (!parent_source)
+ return;
+
+ // TODO(enne): these walks could be done in one step.
+ RecursivelyDetachBeginFrameSource(child_frame_sink_id, parent_source);
+ for (auto source_iter : registered_sources_)
+ RecursivelyAttachBeginFrameSource(source_iter.second, source_iter.first);
+}
+
+void FrameSinkManagerImpl::DropTemporaryReference(const SurfaceId& surface_id) {
+ surface_manager_.DropTemporaryReference(surface_id);
+}
+
+void FrameSinkManagerImpl::RegisterFrameSinkManagerClient(
+ const FrameSinkId& frame_sink_id,
+ FrameSinkManagerClient* client) {
+ DCHECK(client);
+
+ clients_[frame_sink_id] = client;
+
+ auto it = frame_sink_source_map_.find(frame_sink_id);
+ if (it != frame_sink_source_map_.end()) {
+ if (it->second.source)
+ client->SetBeginFrameSource(it->second.source);
+ }
+}
+
+void FrameSinkManagerImpl::UnregisterFrameSinkManagerClient(
+ const FrameSinkId& frame_sink_id) {
+ auto client_iter = clients_.find(frame_sink_id);
+ DCHECK(client_iter != clients_.end());
+
+ auto source_iter = frame_sink_source_map_.find(frame_sink_id);
+ if (source_iter != frame_sink_source_map_.end()) {
+ if (source_iter->second.source)
+ client_iter->second->SetBeginFrameSource(nullptr);
+ }
+ clients_.erase(client_iter);
+}
+
+void FrameSinkManagerImpl::RegisterBeginFrameSource(
+ cc::BeginFrameSource* source,
+ const FrameSinkId& frame_sink_id) {
+ DCHECK(source);
+ DCHECK_EQ(registered_sources_.count(source), 0u);
+
+ registered_sources_[source] = frame_sink_id;
+ RecursivelyAttachBeginFrameSource(frame_sink_id, source);
+
+ primary_source_.OnBeginFrameSourceAdded(source);
+}
+
+void FrameSinkManagerImpl::UnregisterBeginFrameSource(
+ cc::BeginFrameSource* source) {
+ DCHECK(source);
+ DCHECK_EQ(registered_sources_.count(source), 1u);
+
+ FrameSinkId frame_sink_id = registered_sources_[source];
+ registered_sources_.erase(source);
+
+ primary_source_.OnBeginFrameSourceRemoved(source);
+
+ if (frame_sink_source_map_.count(frame_sink_id) == 0u)
+ return;
+
+ // TODO(enne): these walks could be done in one step.
+ // Remove this begin frame source from its subtree.
+ RecursivelyDetachBeginFrameSource(frame_sink_id, source);
+ // Then flush every remaining registered source to fix any sources that
+ // became null because of the previous step but that have an alternative.
+ for (auto source_iter : registered_sources_)
+ RecursivelyAttachBeginFrameSource(source_iter.second, source_iter.first);
+}
+
+cc::BeginFrameSource* FrameSinkManagerImpl::GetPrimaryBeginFrameSource() {
+ return &primary_source_;
+}
+
+void FrameSinkManagerImpl::RecursivelyAttachBeginFrameSource(
+ const FrameSinkId& frame_sink_id,
+ cc::BeginFrameSource* source) {
+ FrameSinkSourceMapping& mapping = frame_sink_source_map_[frame_sink_id];
+ if (!mapping.source) {
+ mapping.source = source;
+ auto client_iter = clients_.find(frame_sink_id);
+ if (client_iter != clients_.end())
+ client_iter->second->SetBeginFrameSource(source);
+ }
+ for (size_t i = 0; i < mapping.children.size(); ++i) {
+ // |frame_sink_source_map_| is a container that can allocate new memory and
+ // move data between buffers. Copy child's FrameSinkId before passing
+ // it to RecursivelyAttachBeginFrameSource so that we don't reference data
+ // inside |frame_sink_source_map_|.
+ FrameSinkId child_copy = mapping.children[i];
+ RecursivelyAttachBeginFrameSource(child_copy, source);
+ }
+}
+
+void FrameSinkManagerImpl::RecursivelyDetachBeginFrameSource(
+ const FrameSinkId& frame_sink_id,
+ cc::BeginFrameSource* source) {
+ auto iter = frame_sink_source_map_.find(frame_sink_id);
+ if (iter == frame_sink_source_map_.end())
+ return;
+ if (iter->second.source == source) {
+ iter->second.source = nullptr;
+ auto client_iter = clients_.find(frame_sink_id);
+ if (client_iter != clients_.end())
+ client_iter->second->SetBeginFrameSource(nullptr);
+ }
+
+ if (!iter->second.has_children() && !clients_.count(frame_sink_id)) {
+ frame_sink_source_map_.erase(iter);
+ return;
+ }
+
+ std::vector<FrameSinkId>& children = iter->second.children;
+ for (size_t i = 0; i < children.size(); ++i) {
+ RecursivelyDetachBeginFrameSource(children[i], source);
+ }
+}
+
+bool FrameSinkManagerImpl::ChildContains(
+ const FrameSinkId& child_frame_sink_id,
+ const FrameSinkId& search_frame_sink_id) const {
+ auto iter = frame_sink_source_map_.find(child_frame_sink_id);
+ if (iter == frame_sink_source_map_.end())
+ return false;
+
+ const std::vector<FrameSinkId>& children = iter->second.children;
+ for (size_t i = 0; i < children.size(); ++i) {
+ if (children[i] == search_frame_sink_id)
+ return true;
+ if (ChildContains(children[i], search_frame_sink_id))
+ return true;
+ }
+ return false;
+}
+
+void FrameSinkManagerImpl::OnSurfaceCreated(const SurfaceInfo& surface_info) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK_GT(surface_info.device_scale_factor(), 0.0f);
+
+ // TODO(kylechar): |client_| will try to find an owner for the temporary
+ // reference to the new surface. With surface synchronization this might not
+ // be necessary, because a surface reference might already exist and no
+ // temporary reference was created. It could be useful to let |client_| know
+ // if it should find an owner.
+ if (client_)
+ client_->OnSurfaceCreated(surface_info);
+}
+
+bool FrameSinkManagerImpl::OnSurfaceDamaged(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack) {
+ return false;
+}
+
+void FrameSinkManagerImpl::OnSurfaceDiscarded(const SurfaceId& surface_id) {}
+
+void FrameSinkManagerImpl::OnSurfaceDestroyed(const SurfaceId& surface_id) {}
+
+void FrameSinkManagerImpl::OnSurfaceDamageExpected(
+ const SurfaceId& surface_id,
+ const cc::BeginFrameArgs& args) {}
+
+void FrameSinkManagerImpl::OnSurfaceWillDraw(const SurfaceId& surface_id) {}
+
+void FrameSinkManagerImpl::OnClientConnectionLost(
+ const FrameSinkId& frame_sink_id) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ if (client_)
+ client_->OnClientConnectionClosed(frame_sink_id);
+}
+
+void FrameSinkManagerImpl::OnPrivateConnectionLost(
+ const FrameSinkId& frame_sink_id) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DestroyCompositorFrameSink(frame_sink_id);
+}
+
+void FrameSinkManagerImpl::DestroyCompositorFrameSink(FrameSinkId sink_id) {
+ compositor_frame_sinks_.erase(sink_id);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/frame_sink_manager_impl.h b/chromium/components/viz/service/frame_sinks/frame_sink_manager_impl.h
new file mode 100644
index 00000000000..e042b874e87
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/frame_sink_manager_impl.h
@@ -0,0 +1,196 @@
+// Copyright 2014 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_FRAME_SINKS_FRAME_SINK_MANAGER_IMPL_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_SINK_MANAGER_IMPL_H_
+
+#include <stdint.h>
+
+#include <unordered_map>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "cc/ipc/frame_sink_manager.mojom.h"
+#include "cc/surfaces/surface_manager.h"
+#include "cc/surfaces/surface_observer.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/service/frame_sinks/primary_begin_frame_source.h"
+#include "components/viz/service/viz_service_export.h"
+#include "gpu/ipc/common/surface_handle.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace cc {
+
+class BeginFrameSource;
+
+namespace test {
+class SurfaceSynchronizationTest;
+}
+
+} // namespace cc
+
+namespace viz {
+
+class DisplayProvider;
+class FrameSinkManagerClient;
+
+// FrameSinkManagerImpl manages BeginFrame hierarchy. This is the implementation
+// detail for FrameSinkManagerImpl.
+class VIZ_SERVICE_EXPORT FrameSinkManagerImpl
+ : public cc::SurfaceObserver,
+ public NON_EXPORTED_BASE(cc::mojom::FrameSinkManager) {
+ public:
+ FrameSinkManagerImpl(DisplayProvider* display_provider = nullptr,
+ cc::SurfaceManager::LifetimeType lifetime_type =
+ cc::SurfaceManager::LifetimeType::SEQUENCES);
+ ~FrameSinkManagerImpl() override;
+
+ // Binds |this| as a FrameSinkManagerImpl for |request| on |task_runner|. On
+ // Mac |task_runner| will be the resize helper task runner. May only be called
+ // once.
+ void BindAndSetClient(cc::mojom::FrameSinkManagerRequest request,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ cc::mojom::FrameSinkManagerClientPtr client);
+
+ // Sets up a direction connection to |client| without using Mojo.
+ void SetLocalClient(cc::mojom::FrameSinkManagerClient* client);
+
+ // cc::mojom::FrameSinkManager implementation:
+ void CreateRootCompositorFrameSink(
+ const FrameSinkId& frame_sink_id,
+ gpu::SurfaceHandle surface_handle,
+ cc::mojom::CompositorFrameSinkAssociatedRequest request,
+ cc::mojom::CompositorFrameSinkPrivateRequest private_request,
+ cc::mojom::CompositorFrameSinkClientPtr client,
+ cc::mojom::DisplayPrivateAssociatedRequest display_private_request)
+ override;
+ void CreateCompositorFrameSink(
+ const FrameSinkId& frame_sink_id,
+ cc::mojom::CompositorFrameSinkRequest request,
+ cc::mojom::CompositorFrameSinkPrivateRequest private_request,
+ cc::mojom::CompositorFrameSinkClientPtr client) override;
+ void RegisterFrameSinkHierarchy(
+ const FrameSinkId& parent_frame_sink_id,
+ const FrameSinkId& child_frame_sink_id) override;
+ void UnregisterFrameSinkHierarchy(
+ const FrameSinkId& parent_frame_sink_id,
+ const FrameSinkId& child_frame_sink_id) override;
+ void DropTemporaryReference(const SurfaceId& surface_id) override;
+
+ // CompositorFrameSinkSupport, hierarchy, and BeginFrameSource can be
+ // registered and unregistered in any order with respect to each other.
+ //
+ // This happens in practice, e.g. the relationship to between ui::Compositor /
+ // DelegatedFrameHost is known before ui::Compositor has a surface/client).
+ // However, DelegatedFrameHost can register itself as a client before its
+ // relationship with the ui::Compositor is known.
+
+ // Associates a FrameSinkManagerClient with the frame_sink_id it uses.
+ // FrameSinkManagerClient and framesink allocators have a 1:1 mapping.
+ // Caller guarantees the client is alive between register/unregister.
+ void RegisterFrameSinkManagerClient(const FrameSinkId& frame_sink_id,
+ FrameSinkManagerClient* client);
+ void UnregisterFrameSinkManagerClient(const FrameSinkId& frame_sink_id);
+
+ // Associates a |source| with a particular framesink. That framesink and
+ // any children of that framesink with valid clients can potentially use
+ // that |source|.
+ void RegisterBeginFrameSource(cc::BeginFrameSource* source,
+ const FrameSinkId& frame_sink_id);
+ void UnregisterBeginFrameSource(cc::BeginFrameSource* source);
+
+ // Returns a stable BeginFrameSource that forwards BeginFrames from the first
+ // available BeginFrameSource.
+ cc::BeginFrameSource* GetPrimaryBeginFrameSource();
+
+ cc::SurfaceManager* surface_manager() { return &surface_manager_; }
+
+ // cc::SurfaceObserver implementation.
+ void OnSurfaceCreated(const SurfaceInfo& surface_info) override;
+ bool OnSurfaceDamaged(const SurfaceId& surface_id,
+ const cc::BeginFrameAck& ack) override;
+ void OnSurfaceDiscarded(const SurfaceId& surface_id) override;
+ void OnSurfaceDestroyed(const SurfaceId& surface_id) override;
+ void OnSurfaceDamageExpected(const SurfaceId& surface_id,
+ const cc::BeginFrameArgs& args) override;
+ void OnSurfaceWillDraw(const SurfaceId& surface_id) override;
+
+ void OnClientConnectionLost(const FrameSinkId& frame_sink_id);
+ void OnPrivateConnectionLost(const FrameSinkId& frame_sink_id);
+
+ // It is necessary to pass |frame_sink_id| by value because the id
+ // is owned by the GpuCompositorFrameSink in the map. When the sink is
+ // removed from the map, |frame_sink_id| would also be destroyed if it were a
+ // reference. But the map can continue to iterate and try to use it. Passing
+ // by value avoids this.
+ void DestroyCompositorFrameSink(FrameSinkId frame_sink_id);
+
+ private:
+ friend class cc::test::SurfaceSynchronizationTest;
+
+ void RecursivelyAttachBeginFrameSource(const FrameSinkId& frame_sink_id,
+ cc::BeginFrameSource* source);
+ void RecursivelyDetachBeginFrameSource(const FrameSinkId& frame_sink_id,
+ cc::BeginFrameSource* source);
+
+ // Returns true if |child framesink| is or has |search_frame_sink_id| as a
+ // child.
+ bool ChildContains(const FrameSinkId& child_frame_sink_id,
+ const FrameSinkId& search_frame_sink_id) const;
+
+ // Begin frame source routing. Both BeginFrameSource and
+ // CompositorFrameSinkSupport pointers guaranteed alive by callers until
+ // unregistered.
+ struct FrameSinkSourceMapping {
+ FrameSinkSourceMapping();
+ FrameSinkSourceMapping(const FrameSinkSourceMapping& other);
+ ~FrameSinkSourceMapping();
+ bool has_children() const { return !children.empty(); }
+ // The currently assigned begin frame source for this client.
+ cc::BeginFrameSource* source = nullptr;
+ // This represents a dag of parent -> children mapping.
+ std::vector<FrameSinkId> children;
+ };
+
+ // Provides a Display for CreateRootCompositorFrameSink().
+ DisplayProvider* const display_provider_;
+
+ base::flat_map<FrameSinkId, FrameSinkManagerClient*> clients_;
+ std::unordered_map<FrameSinkId, FrameSinkSourceMapping, FrameSinkIdHash>
+ frame_sink_source_map_;
+
+ // Set of BeginFrameSource along with associated FrameSinkIds. Any child
+ // that is implicitly using this framesink must be reachable by the
+ // parent in the dag.
+ std::unordered_map<cc::BeginFrameSource*, FrameSinkId> registered_sources_;
+
+ PrimaryBeginFrameSource primary_source_;
+
+ // |surface_manager_| should be placed under |primary_source_| so that all
+ // surfaces are destroyed before |primary_source_|.
+ cc::SurfaceManager surface_manager_;
+
+ std::unordered_map<FrameSinkId,
+ std::unique_ptr<cc::mojom::CompositorFrameSink>,
+ FrameSinkIdHash>
+ compositor_frame_sinks_;
+
+ THREAD_CHECKER(thread_checker_);
+
+ // This will point to |client_ptr_| if using Mojo or a provided client if
+ // directly connected. Use this to make function calls.
+ cc::mojom::FrameSinkManagerClient* client_ = nullptr;
+
+ cc::mojom::FrameSinkManagerClientPtr client_ptr_;
+ mojo::Binding<cc::mojom::FrameSinkManager> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameSinkManagerImpl);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_FRAME_SINK_MANAGER_IMPL_H_
diff --git a/chromium/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc b/chromium/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc
new file mode 100644
index 00000000000..8fd888d7ba1
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/frame_sink_manager_unittest.cc
@@ -0,0 +1,529 @@
+// Copyright 2016 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 <stddef.h>
+
+#include "cc/scheduler/begin_frame_source.h"
+#include "cc/test/begin_frame_source_test.h"
+#include "cc/test/fake_external_begin_frame_source.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_client.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace viz {
+
+namespace {
+
+constexpr FrameSinkId kArbitraryFrameSinkId(1, 1);
+}
+
+class FakeFrameSinkManagerClient : public FrameSinkManagerClient {
+ public:
+ explicit FakeFrameSinkManagerClient(const FrameSinkId& frame_sink_id)
+ : source_(nullptr), manager_(nullptr), frame_sink_id_(frame_sink_id) {}
+
+ FakeFrameSinkManagerClient(const FrameSinkId& frame_sink_id,
+ FrameSinkManagerImpl* manager)
+ : source_(nullptr), manager_(nullptr), frame_sink_id_(frame_sink_id) {
+ DCHECK(manager);
+ Register(manager);
+ }
+
+ ~FakeFrameSinkManagerClient() override {
+ if (manager_) {
+ Unregister();
+ }
+ EXPECT_EQ(nullptr, source_);
+ }
+
+ cc::BeginFrameSource* source() { return source_; }
+ const FrameSinkId& frame_sink_id() { return frame_sink_id_; }
+
+ void Register(FrameSinkManagerImpl* manager) {
+ EXPECT_EQ(nullptr, manager_);
+ manager_ = manager;
+ manager_->RegisterFrameSinkManagerClient(frame_sink_id_, this);
+ }
+
+ void Unregister() {
+ EXPECT_NE(manager_, nullptr);
+ manager_->UnregisterFrameSinkManagerClient(frame_sink_id_);
+ manager_ = nullptr;
+ }
+
+ // FrameSinkManagerClient implementation.
+ void SetBeginFrameSource(cc::BeginFrameSource* begin_frame_source) override {
+ DCHECK(!source_ || !begin_frame_source);
+ source_ = begin_frame_source;
+ }
+
+ private:
+ cc::BeginFrameSource* source_;
+ FrameSinkManagerImpl* manager_;
+ FrameSinkId frame_sink_id_;
+};
+
+class FrameSinkManagerTest : public testing::Test {
+ public:
+ FrameSinkManagerTest() = default;
+
+ ~FrameSinkManagerTest() override = default;
+
+ protected:
+ FrameSinkManagerImpl manager_;
+};
+
+TEST_F(FrameSinkManagerTest, SingleClients) {
+ FakeFrameSinkManagerClient client(FrameSinkId(1, 1));
+ FakeFrameSinkManagerClient other_client(FrameSinkId(2, 2));
+ cc::StubBeginFrameSource source;
+
+ EXPECT_EQ(nullptr, client.source());
+ EXPECT_EQ(nullptr, other_client.source());
+ client.Register(&manager_);
+ other_client.Register(&manager_);
+ EXPECT_EQ(nullptr, client.source());
+ EXPECT_EQ(nullptr, other_client.source());
+
+ // Test setting unsetting BFS
+ manager_.RegisterBeginFrameSource(&source, client.frame_sink_id());
+ EXPECT_EQ(&source, client.source());
+ EXPECT_EQ(nullptr, other_client.source());
+ manager_.UnregisterBeginFrameSource(&source);
+ EXPECT_EQ(nullptr, client.source());
+ EXPECT_EQ(nullptr, other_client.source());
+
+ // Set BFS for other namespace
+ manager_.RegisterBeginFrameSource(&source, other_client.frame_sink_id());
+ EXPECT_EQ(&source, other_client.source());
+ EXPECT_EQ(nullptr, client.source());
+ manager_.UnregisterBeginFrameSource(&source);
+ EXPECT_EQ(nullptr, client.source());
+ EXPECT_EQ(nullptr, other_client.source());
+
+ // Re-set BFS for original
+ manager_.RegisterBeginFrameSource(&source, client.frame_sink_id());
+ EXPECT_EQ(&source, client.source());
+ manager_.UnregisterBeginFrameSource(&source);
+ EXPECT_EQ(nullptr, client.source());
+}
+
+// This test verifies that a client is still connected to the BeginFrameSource
+// after restart.
+TEST_F(FrameSinkManagerTest, ClientRestart) {
+ FakeFrameSinkManagerClient client(kArbitraryFrameSinkId);
+ cc::StubBeginFrameSource source;
+ client.Register(&manager_);
+
+ manager_.RegisterBeginFrameSource(&source, kArbitraryFrameSinkId);
+ EXPECT_EQ(&source, client.source());
+
+ // |client| is disconnect from |source| after Unregister.
+ client.Unregister();
+ EXPECT_EQ(nullptr, client.source());
+
+ // |client| is reconnected with |source| after re-Register.
+ client.Register(&manager_);
+ EXPECT_EQ(&source, client.source());
+
+ manager_.UnregisterBeginFrameSource(&source);
+ EXPECT_EQ(nullptr, client.source());
+}
+
+// This test verifies that a PrimaryBeginFrameSource will receive BeginFrames
+// from the first BeginFrameSource registered. If that BeginFrameSource goes
+// away then it will receive BeginFrames from the second BeginFrameSource.
+TEST_F(FrameSinkManagerTest, PrimaryBeginFrameSource) {
+ // This PrimaryBeginFrameSource should track the first BeginFrameSource
+ // registered with the SurfaceManager.
+ testing::NiceMock<cc::MockBeginFrameObserver> obs;
+ cc::BeginFrameSource* begin_frame_source =
+ manager_.GetPrimaryBeginFrameSource();
+ begin_frame_source->AddObserver(&obs);
+
+ FakeFrameSinkManagerClient root1(FrameSinkId(1, 1), &manager_);
+ std::unique_ptr<cc::FakeExternalBeginFrameSource> external_source1 =
+ base::MakeUnique<cc::FakeExternalBeginFrameSource>(60.f, false);
+ manager_.RegisterBeginFrameSource(external_source1.get(),
+ root1.frame_sink_id());
+
+ FakeFrameSinkManagerClient root2(FrameSinkId(2, 2), &manager_);
+ std::unique_ptr<cc::FakeExternalBeginFrameSource> external_source2 =
+ base::MakeUnique<cc::FakeExternalBeginFrameSource>(60.f, false);
+ manager_.RegisterBeginFrameSource(external_source2.get(),
+ root2.frame_sink_id());
+
+ cc::BeginFrameArgs args =
+ cc::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
+
+ // Ticking |external_source2| does not propagate to |begin_frame_source|.
+ {
+ EXPECT_CALL(obs, OnBeginFrame(args)).Times(0);
+ external_source2->TestOnBeginFrame(args);
+ testing::Mock::VerifyAndClearExpectations(&obs);
+ }
+
+ // Ticking |external_source1| does propagate to |begin_frame_source| and
+ // |obs|.
+ {
+ EXPECT_CALL(obs, OnBeginFrame(testing::_)).Times(1);
+ external_source1->TestOnBeginFrame(args);
+ testing::Mock::VerifyAndClearExpectations(&obs);
+ }
+
+ // Getting rid of |external_source1| means those BeginFrames will not
+ // propagate. Instead, |external_source2|'s BeginFrames will propagate
+ // to |begin_frame_source|.
+ {
+ manager_.UnregisterBeginFrameSource(external_source1.get());
+ EXPECT_CALL(obs, OnBeginFrame(testing::_)).Times(0);
+ external_source1->TestOnBeginFrame(args);
+ testing::Mock::VerifyAndClearExpectations(&obs);
+
+ EXPECT_CALL(obs, OnBeginFrame(testing::_)).Times(1);
+ external_source2->TestOnBeginFrame(args);
+ testing::Mock::VerifyAndClearExpectations(&obs);
+ }
+
+ // Tear down
+ manager_.UnregisterBeginFrameSource(external_source2.get());
+ begin_frame_source->RemoveObserver(&obs);
+}
+
+TEST_F(FrameSinkManagerTest, MultipleDisplays) {
+ cc::StubBeginFrameSource root1_source;
+ cc::StubBeginFrameSource root2_source;
+
+ // root1 -> A -> B
+ // root2 -> C
+ FakeFrameSinkManagerClient root1(FrameSinkId(1, 1), &manager_);
+ FakeFrameSinkManagerClient root2(FrameSinkId(2, 2), &manager_);
+ FakeFrameSinkManagerClient client_a(FrameSinkId(3, 3), &manager_);
+ FakeFrameSinkManagerClient client_b(FrameSinkId(4, 4), &manager_);
+ FakeFrameSinkManagerClient client_c(FrameSinkId(5, 5), &manager_);
+
+ manager_.RegisterBeginFrameSource(&root1_source, root1.frame_sink_id());
+ manager_.RegisterBeginFrameSource(&root2_source, root2.frame_sink_id());
+ EXPECT_EQ(root1.source(), &root1_source);
+ EXPECT_EQ(root2.source(), &root2_source);
+
+ // Set up initial hierarchy.
+ manager_.RegisterFrameSinkHierarchy(root1.frame_sink_id(),
+ client_a.frame_sink_id());
+ EXPECT_EQ(client_a.source(), root1.source());
+ manager_.RegisterFrameSinkHierarchy(client_a.frame_sink_id(),
+ client_b.frame_sink_id());
+ EXPECT_EQ(client_b.source(), root1.source());
+ manager_.RegisterFrameSinkHierarchy(root2.frame_sink_id(),
+ client_c.frame_sink_id());
+ EXPECT_EQ(client_c.source(), root2.source());
+
+ // Attach A into root2's subtree, like a window moving across displays.
+ // root1 -> A -> B
+ // root2 -> C -> A -> B
+ manager_.RegisterFrameSinkHierarchy(client_c.frame_sink_id(),
+ client_a.frame_sink_id());
+ // With the heuristic of just keeping existing BFS in the face of multiple,
+ // no client sources should change.
+ EXPECT_EQ(client_a.source(), root1.source());
+ EXPECT_EQ(client_b.source(), root1.source());
+ EXPECT_EQ(client_c.source(), root2.source());
+
+ // Detach A from root1. A and B should now be updated to root2.
+ manager_.UnregisterFrameSinkHierarchy(root1.frame_sink_id(),
+ client_a.frame_sink_id());
+ EXPECT_EQ(client_a.source(), root2.source());
+ EXPECT_EQ(client_b.source(), root2.source());
+ EXPECT_EQ(client_c.source(), root2.source());
+
+ // Detach root1 from BFS. root1 should now have no source.
+ manager_.UnregisterBeginFrameSource(&root1_source);
+ EXPECT_EQ(nullptr, root1.source());
+ EXPECT_NE(nullptr, root2.source());
+
+ // Detatch root2 from BFS.
+ manager_.UnregisterBeginFrameSource(&root2_source);
+ EXPECT_EQ(nullptr, client_a.source());
+ EXPECT_EQ(nullptr, client_b.source());
+ EXPECT_EQ(nullptr, client_c.source());
+ EXPECT_EQ(nullptr, root2.source());
+
+ // Cleanup hierarchy.
+ manager_.UnregisterFrameSinkHierarchy(root2.frame_sink_id(),
+ client_c.frame_sink_id());
+ manager_.UnregisterFrameSinkHierarchy(client_c.frame_sink_id(),
+ client_a.frame_sink_id());
+ manager_.UnregisterFrameSinkHierarchy(client_a.frame_sink_id(),
+ client_b.frame_sink_id());
+}
+
+// This test verifies that a BeginFrameSource path to the root from a
+// FrameSinkId is preserved even if that FrameSinkId has no children
+// and does not have a corresponding FrameSinkManagerClient.
+TEST_F(FrameSinkManagerTest, ParentWithoutClientRetained) {
+ cc::StubBeginFrameSource root_source;
+
+ constexpr FrameSinkId kFrameSinkIdRoot(1, 1);
+ constexpr FrameSinkId kFrameSinkIdA(2, 2);
+ constexpr FrameSinkId kFrameSinkIdB(3, 3);
+ constexpr FrameSinkId kFrameSinkIdC(4, 4);
+
+ FakeFrameSinkManagerClient root(kFrameSinkIdRoot, &manager_);
+ FakeFrameSinkManagerClient client_b(kFrameSinkIdB, &manager_);
+ FakeFrameSinkManagerClient client_c(kFrameSinkIdC, &manager_);
+
+ manager_.RegisterBeginFrameSource(&root_source, root.frame_sink_id());
+ EXPECT_EQ(&root_source, root.source());
+
+ // Set up initial hierarchy: root -> A -> B.
+ // Note that A does not have a FrameSinkManagerClient.
+ manager_.RegisterFrameSinkHierarchy(kFrameSinkIdRoot, kFrameSinkIdA);
+ manager_.RegisterFrameSinkHierarchy(kFrameSinkIdA, kFrameSinkIdB);
+ // The root's BeginFrameSource should propagate to B.
+ EXPECT_EQ(root.source(), client_b.source());
+
+ // Unregister B, and attach C to A: root -> A -> C
+ manager_.UnregisterFrameSinkHierarchy(kFrameSinkIdA, kFrameSinkIdB);
+ EXPECT_EQ(nullptr, client_b.source());
+ manager_.RegisterFrameSinkHierarchy(kFrameSinkIdA, kFrameSinkIdC);
+ // The root's BeginFrameSource should propagate to C.
+ EXPECT_EQ(root.source(), client_c.source());
+
+ manager_.UnregisterBeginFrameSource(&root_source);
+ EXPECT_EQ(nullptr, root.source());
+ EXPECT_EQ(nullptr, client_c.source());
+}
+
+// This test sets up the same hierarchy as ParentWithoutClientRetained.
+// However, this unit test registers the BeginFrameSource AFTER C
+// has been attached to A. This test verifies that the BeginFrameSource
+// propagates all the way to C.
+TEST_F(FrameSinkManagerTest,
+ ParentWithoutClientRetained_LateBeginFrameRegistration) {
+ cc::StubBeginFrameSource root_source;
+
+ constexpr FrameSinkId kFrameSinkIdRoot(1, 1);
+ constexpr FrameSinkId kFrameSinkIdA(2, 2);
+ constexpr FrameSinkId kFrameSinkIdB(3, 3);
+ constexpr FrameSinkId kFrameSinkIdC(4, 4);
+
+ FakeFrameSinkManagerClient root(kFrameSinkIdRoot, &manager_);
+ FakeFrameSinkManagerClient client_b(kFrameSinkIdB, &manager_);
+ FakeFrameSinkManagerClient client_c(kFrameSinkIdC, &manager_);
+
+ // Set up initial hierarchy: root -> A -> B.
+ // Note that A does not have a FrameSinkManagerClient.
+ manager_.RegisterFrameSinkHierarchy(kFrameSinkIdRoot, kFrameSinkIdA);
+ manager_.RegisterFrameSinkHierarchy(kFrameSinkIdA, kFrameSinkIdB);
+ // The root does not yet have a BeginFrameSource so client B should not have
+ // one either.
+ EXPECT_EQ(nullptr, client_b.source());
+
+ // Unregister B, and attach C to A: root -> A -> C
+ manager_.UnregisterFrameSinkHierarchy(kFrameSinkIdA, kFrameSinkIdB);
+ manager_.RegisterFrameSinkHierarchy(kFrameSinkIdA, kFrameSinkIdC);
+
+ // Registering a BeginFrameSource at the root should propagate it to C.
+ manager_.RegisterBeginFrameSource(&root_source, root.frame_sink_id());
+ // The root's BeginFrameSource should propagate to C.
+ EXPECT_EQ(&root_source, root.source());
+ EXPECT_EQ(root.source(), client_c.source());
+
+ manager_.UnregisterBeginFrameSource(&root_source);
+ EXPECT_EQ(nullptr, root.source());
+ EXPECT_EQ(nullptr, client_c.source());
+}
+
+// In practice, registering and unregistering both parent/child relationships
+// and FrameSinkManagerClients can happen in any ordering with respect to
+// each other. These following tests verify that all the data structures
+// are properly set up and cleaned up under the four permutations of orderings
+// of this nesting.
+
+class SurfaceManagerOrderingTest : public FrameSinkManagerTest {
+ public:
+ SurfaceManagerOrderingTest()
+ : client_a_(FrameSinkId(1, 1)),
+ client_b_(FrameSinkId(2, 2)),
+ client_c_(FrameSinkId(3, 3)),
+ hierarchy_registered_(false),
+ clients_registered_(false),
+ bfs_registered_(false) {
+ AssertCorrectBFSState();
+ }
+
+ ~SurfaceManagerOrderingTest() override {
+ EXPECT_FALSE(hierarchy_registered_);
+ EXPECT_FALSE(clients_registered_);
+ EXPECT_FALSE(bfs_registered_);
+ AssertCorrectBFSState();
+ }
+
+ void RegisterHierarchy() {
+ DCHECK(!hierarchy_registered_);
+ hierarchy_registered_ = true;
+ manager_.RegisterFrameSinkHierarchy(client_a_.frame_sink_id(),
+ client_b_.frame_sink_id());
+ manager_.RegisterFrameSinkHierarchy(client_b_.frame_sink_id(),
+ client_c_.frame_sink_id());
+ AssertCorrectBFSState();
+ }
+ void UnregisterHierarchy() {
+ DCHECK(hierarchy_registered_);
+ hierarchy_registered_ = false;
+ manager_.UnregisterFrameSinkHierarchy(client_a_.frame_sink_id(),
+ client_b_.frame_sink_id());
+ manager_.UnregisterFrameSinkHierarchy(client_b_.frame_sink_id(),
+ client_c_.frame_sink_id());
+ AssertCorrectBFSState();
+ }
+
+ void RegisterClients() {
+ DCHECK(!clients_registered_);
+ clients_registered_ = true;
+ client_a_.Register(&manager_);
+ client_b_.Register(&manager_);
+ client_c_.Register(&manager_);
+ AssertCorrectBFSState();
+ }
+
+ void UnregisterClients() {
+ DCHECK(clients_registered_);
+ clients_registered_ = false;
+ client_a_.Unregister();
+ client_b_.Unregister();
+ client_c_.Unregister();
+ AssertCorrectBFSState();
+ }
+
+ void RegisterBFS() {
+ DCHECK(!bfs_registered_);
+ bfs_registered_ = true;
+ manager_.RegisterBeginFrameSource(&source_, client_a_.frame_sink_id());
+ AssertCorrectBFSState();
+ }
+ void UnregisterBFS() {
+ DCHECK(bfs_registered_);
+ bfs_registered_ = false;
+ manager_.UnregisterBeginFrameSource(&source_);
+ AssertCorrectBFSState();
+ }
+
+ void AssertEmptyBFS() {
+ EXPECT_EQ(nullptr, client_a_.source());
+ EXPECT_EQ(nullptr, client_b_.source());
+ EXPECT_EQ(nullptr, client_c_.source());
+ }
+
+ void AssertAllValidBFS() {
+ EXPECT_EQ(&source_, client_a_.source());
+ EXPECT_EQ(&source_, client_b_.source());
+ EXPECT_EQ(&source_, client_c_.source());
+ }
+
+ protected:
+ void AssertCorrectBFSState() {
+ if (!clients_registered_ || !bfs_registered_) {
+ AssertEmptyBFS();
+ return;
+ }
+ if (!hierarchy_registered_) {
+ // A valid but not attached to anything.
+ EXPECT_EQ(&source_, client_a_.source());
+ EXPECT_EQ(nullptr, client_b_.source());
+ EXPECT_EQ(nullptr, client_c_.source());
+ return;
+ }
+
+ AssertAllValidBFS();
+ }
+
+ cc::StubBeginFrameSource source_;
+ // A -> B -> C hierarchy, with A always having the BFS.
+ FakeFrameSinkManagerClient client_a_;
+ FakeFrameSinkManagerClient client_b_;
+ FakeFrameSinkManagerClient client_c_;
+
+ bool hierarchy_registered_;
+ bool clients_registered_;
+ bool bfs_registered_;
+};
+
+enum RegisterOrder { REGISTER_HIERARCHY_FIRST, REGISTER_CLIENTS_FIRST };
+enum UnregisterOrder { UNREGISTER_HIERARCHY_FIRST, UNREGISTER_CLIENTS_FIRST };
+enum BFSOrder { BFS_FIRST, BFS_SECOND, BFS_THIRD };
+
+static const RegisterOrder kRegisterOrderList[] = {REGISTER_HIERARCHY_FIRST,
+ REGISTER_CLIENTS_FIRST};
+static const UnregisterOrder kUnregisterOrderList[] = {
+ UNREGISTER_HIERARCHY_FIRST, UNREGISTER_CLIENTS_FIRST};
+static const BFSOrder kBFSOrderList[] = {BFS_FIRST, BFS_SECOND, BFS_THIRD};
+
+class SurfaceManagerOrderingParamTest
+ : public SurfaceManagerOrderingTest,
+ public ::testing::WithParamInterface<
+ std::tr1::tuple<RegisterOrder, UnregisterOrder, BFSOrder>> {};
+
+TEST_P(SurfaceManagerOrderingParamTest, Ordering) {
+ // Test the four permutations of client/hierarchy setting/unsetting and test
+ // each place the BFS can be added and removed. The BFS and the
+ // client/hierarchy are less related, so BFS is tested independently instead
+ // of every permutation of BFS setting and unsetting.
+ // The register/unregister functions themselves test most of the state.
+ RegisterOrder register_order = std::tr1::get<0>(GetParam());
+ UnregisterOrder unregister_order = std::tr1::get<1>(GetParam());
+ BFSOrder bfs_order = std::tr1::get<2>(GetParam());
+
+ // Attach everything up in the specified order.
+ if (bfs_order == BFS_FIRST)
+ RegisterBFS();
+
+ if (register_order == REGISTER_HIERARCHY_FIRST)
+ RegisterHierarchy();
+ else
+ RegisterClients();
+
+ if (bfs_order == BFS_SECOND)
+ RegisterBFS();
+
+ if (register_order == REGISTER_HIERARCHY_FIRST)
+ RegisterClients();
+ else
+ RegisterHierarchy();
+
+ if (bfs_order == BFS_THIRD)
+ RegisterBFS();
+
+ // Everything hooked up, so should be valid.
+ AssertAllValidBFS();
+
+ // Detach everything in the specified order.
+ if (bfs_order == BFS_THIRD)
+ UnregisterBFS();
+
+ if (unregister_order == UNREGISTER_HIERARCHY_FIRST)
+ UnregisterHierarchy();
+ else
+ UnregisterClients();
+
+ if (bfs_order == BFS_SECOND)
+ UnregisterBFS();
+
+ if (unregister_order == UNREGISTER_HIERARCHY_FIRST)
+ UnregisterClients();
+ else
+ UnregisterHierarchy();
+
+ if (bfs_order == BFS_FIRST)
+ UnregisterBFS();
+}
+
+INSTANTIATE_TEST_CASE_P(
+ SurfaceManagerOrderingParamTestInstantiation,
+ SurfaceManagerOrderingParamTest,
+ ::testing::Combine(::testing::ValuesIn(kRegisterOrderList),
+ ::testing::ValuesIn(kUnregisterOrderList),
+ ::testing::ValuesIn(kBFSOrderList)));
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/gpu_compositor_frame_sink.cc b/chromium/components/viz/service/frame_sinks/gpu_compositor_frame_sink.cc
new file mode 100644
index 00000000000..12e0d5bc124
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/gpu_compositor_frame_sink.cc
@@ -0,0 +1,106 @@
+// 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 "components/viz/service/frame_sinks/gpu_compositor_frame_sink.h"
+
+#include <utility>
+
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+
+namespace viz {
+
+GpuCompositorFrameSink::GpuCompositorFrameSink(
+ FrameSinkManagerImpl* frame_sink_manager,
+ const FrameSinkId& frame_sink_id,
+ cc::mojom::CompositorFrameSinkRequest request,
+ cc::mojom::CompositorFrameSinkPrivateRequest
+ compositor_frame_sink_private_request,
+ cc::mojom::CompositorFrameSinkClientPtr client)
+ : support_(CompositorFrameSinkSupport::Create(
+ this,
+ frame_sink_manager,
+ frame_sink_id,
+ false /* is_root */,
+ true /* handles_frame_sink_id_invalidation */,
+ true /* needs_sync_points */)),
+ client_(std::move(client)),
+ compositor_frame_sink_binding_(this, std::move(request)),
+ compositor_frame_sink_private_binding_(
+ this,
+ std::move(compositor_frame_sink_private_request)) {
+ compositor_frame_sink_binding_.set_connection_error_handler(base::Bind(
+ &GpuCompositorFrameSink::OnClientConnectionLost, base::Unretained(this)));
+ compositor_frame_sink_private_binding_.set_connection_error_handler(
+ base::Bind(&GpuCompositorFrameSink::OnPrivateConnectionLost,
+ base::Unretained(this)));
+}
+
+GpuCompositorFrameSink::~GpuCompositorFrameSink() = default;
+
+void GpuCompositorFrameSink::SetNeedsBeginFrame(bool needs_begin_frame) {
+ support_->SetNeedsBeginFrame(needs_begin_frame);
+}
+
+void GpuCompositorFrameSink::SubmitCompositorFrame(
+ const LocalSurfaceId& local_surface_id,
+ cc::CompositorFrame frame) {
+ if (!support_->SubmitCompositorFrame(local_surface_id, std::move(frame))) {
+ compositor_frame_sink_binding_.CloseWithReason(
+ 1, "Surface invariants violation");
+ OnClientConnectionLost();
+ }
+}
+
+void GpuCompositorFrameSink::DidNotProduceFrame(
+ const cc::BeginFrameAck& begin_frame_ack) {
+ support_->DidNotProduceFrame(begin_frame_ack);
+}
+
+void GpuCompositorFrameSink::DidReceiveCompositorFrameAck(
+ const std::vector<cc::ReturnedResource>& resources) {
+ if (client_)
+ client_->DidReceiveCompositorFrameAck(resources);
+}
+
+void GpuCompositorFrameSink::ClaimTemporaryReference(
+ const SurfaceId& surface_id) {
+ support_->ClaimTemporaryReference(surface_id);
+}
+
+void GpuCompositorFrameSink::RequestCopyOfSurface(
+ std::unique_ptr<cc::CopyOutputRequest> request) {
+ support_->RequestCopyOfSurface(std::move(request));
+}
+
+void GpuCompositorFrameSink::OnBeginFrame(const cc::BeginFrameArgs& args) {
+ if (client_)
+ client_->OnBeginFrame(args);
+}
+
+void GpuCompositorFrameSink::OnBeginFramePausedChanged(bool paused) {
+ if (client_)
+ client_->OnBeginFramePausedChanged(paused);
+}
+
+void GpuCompositorFrameSink::ReclaimResources(
+ const std::vector<cc::ReturnedResource>& resources) {
+ if (client_)
+ client_->ReclaimResources(resources);
+}
+
+void GpuCompositorFrameSink::WillDrawSurface(
+ const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) {}
+
+void GpuCompositorFrameSink::OnClientConnectionLost() {
+ support_->frame_sink_manager()->OnClientConnectionLost(
+ support_->frame_sink_id());
+}
+
+void GpuCompositorFrameSink::OnPrivateConnectionLost() {
+ support_->frame_sink_manager()->OnPrivateConnectionLost(
+ support_->frame_sink_id());
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/gpu_compositor_frame_sink.h b/chromium/components/viz/service/frame_sinks/gpu_compositor_frame_sink.h
new file mode 100644
index 00000000000..54a40561929
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/gpu_compositor_frame_sink.h
@@ -0,0 +1,74 @@
+// 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.
+
+#ifndef COMPONENTS_VIZ_SERVICE_FRAME_SINKS_GPU_COMPOSITOR_FRAME_SINK_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_GPU_COMPOSITOR_FRAME_SINK_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/ipc/compositor_frame_sink.mojom.h"
+#include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support_client.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace viz {
+
+// Server side representation of a WindowSurface.
+class GpuCompositorFrameSink
+ : public NON_EXPORTED_BASE(CompositorFrameSinkSupportClient),
+ public NON_EXPORTED_BASE(cc::mojom::CompositorFrameSink),
+ public NON_EXPORTED_BASE(cc::mojom::CompositorFrameSinkPrivate) {
+ public:
+ GpuCompositorFrameSink(
+ FrameSinkManagerImpl* frame_sink_manager,
+ const FrameSinkId& frame_sink_id,
+ cc::mojom::CompositorFrameSinkRequest request,
+ cc::mojom::CompositorFrameSinkPrivateRequest private_request,
+ cc::mojom::CompositorFrameSinkClientPtr client);
+
+ ~GpuCompositorFrameSink() override;
+
+ // cc::mojom::CompositorFrameSink:
+ void SetNeedsBeginFrame(bool needs_begin_frame) override;
+ void SubmitCompositorFrame(const LocalSurfaceId& local_surface_id,
+ cc::CompositorFrame frame) override;
+ void DidNotProduceFrame(const cc::BeginFrameAck& begin_frame_ack) override;
+
+ // cc::mojom::CompositorFrameSinkPrivate:
+ void ClaimTemporaryReference(const SurfaceId& surface_id) override;
+ void RequestCopyOfSurface(
+ std::unique_ptr<cc::CopyOutputRequest> request) override;
+
+ private:
+ // CompositorFrameSinkSupportClient implementation:
+ void DidReceiveCompositorFrameAck(
+ const std::vector<cc::ReturnedResource>& resources) override;
+ void OnBeginFrame(const cc::BeginFrameArgs& args) override;
+ void OnBeginFramePausedChanged(bool paused) override;
+ void ReclaimResources(
+ const std::vector<cc::ReturnedResource>& resources) override;
+ void WillDrawSurface(const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) override;
+
+ void OnClientConnectionLost();
+ void OnPrivateConnectionLost();
+
+ std::unique_ptr<CompositorFrameSinkSupport> support_;
+
+ cc::mojom::CompositorFrameSinkClientPtr client_;
+ mojo::Binding<cc::mojom::CompositorFrameSink> compositor_frame_sink_binding_;
+ mojo::Binding<cc::mojom::CompositorFrameSinkPrivate>
+ compositor_frame_sink_private_binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuCompositorFrameSink);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_GPU_COMPOSITOR_FRAME_SINK_H_
diff --git a/chromium/components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.cc b/chromium/components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.cc
new file mode 100644
index 00000000000..610d3f85c08
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.cc
@@ -0,0 +1,163 @@
+// 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/frame_sinks/gpu_root_compositor_frame_sink.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "components/viz/service/display/display.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+
+namespace viz {
+
+GpuRootCompositorFrameSink::GpuRootCompositorFrameSink(
+ FrameSinkManagerImpl* frame_sink_manager,
+ const FrameSinkId& frame_sink_id,
+ std::unique_ptr<Display> display,
+ std::unique_ptr<cc::BeginFrameSource> begin_frame_source,
+ cc::mojom::CompositorFrameSinkAssociatedRequest request,
+ cc::mojom::CompositorFrameSinkPrivateRequest
+ compositor_frame_sink_private_request,
+ cc::mojom::CompositorFrameSinkClientPtr client,
+ cc::mojom::DisplayPrivateAssociatedRequest display_private_request)
+ : support_(CompositorFrameSinkSupport::Create(
+ this,
+ frame_sink_manager,
+ frame_sink_id,
+ true /* is_root */,
+ true /* handles_frame_sink_id_invalidation */,
+ true /* needs_sync_points */)),
+ display_begin_frame_source_(std::move(begin_frame_source)),
+ display_(std::move(display)),
+ client_(std::move(client)),
+ compositor_frame_sink_binding_(this, std::move(request)),
+ compositor_frame_sink_private_binding_(
+ this,
+ std::move(compositor_frame_sink_private_request)),
+ display_private_binding_(this, std::move(display_private_request)) {
+ DCHECK(display_begin_frame_source_);
+ compositor_frame_sink_binding_.set_connection_error_handler(
+ base::Bind(&GpuRootCompositorFrameSink::OnClientConnectionLost,
+ base::Unretained(this)));
+ compositor_frame_sink_private_binding_.set_connection_error_handler(
+ base::Bind(&GpuRootCompositorFrameSink::OnPrivateConnectionLost,
+ base::Unretained(this)));
+ frame_sink_manager->RegisterBeginFrameSource(
+ display_begin_frame_source_.get(), frame_sink_id);
+ display_->Initialize(this, frame_sink_manager->surface_manager());
+}
+
+GpuRootCompositorFrameSink::~GpuRootCompositorFrameSink() {
+ support_->frame_sink_manager()->UnregisterBeginFrameSource(
+ display_begin_frame_source_.get());
+}
+
+void GpuRootCompositorFrameSink::SetDisplayVisible(bool visible) {
+ DCHECK(display_);
+ display_->SetVisible(visible);
+}
+
+void GpuRootCompositorFrameSink::ResizeDisplay(const gfx::Size& size) {
+ DCHECK(display_);
+ display_->Resize(size);
+}
+
+void GpuRootCompositorFrameSink::SetDisplayColorSpace(
+ const gfx::ColorSpace& color_space) {
+ DCHECK(display_);
+ display_->SetColorSpace(color_space, color_space);
+}
+
+void GpuRootCompositorFrameSink::SetOutputIsSecure(bool secure) {
+ DCHECK(display_);
+ display_->SetOutputIsSecure(secure);
+}
+
+void GpuRootCompositorFrameSink::SetLocalSurfaceId(
+ const LocalSurfaceId& local_surface_id,
+ float scale_factor) {
+ display_->SetLocalSurfaceId(local_surface_id, scale_factor);
+}
+
+void GpuRootCompositorFrameSink::SetNeedsBeginFrame(bool needs_begin_frame) {
+ support_->SetNeedsBeginFrame(needs_begin_frame);
+}
+
+void GpuRootCompositorFrameSink::SubmitCompositorFrame(
+ const LocalSurfaceId& local_surface_id,
+ cc::CompositorFrame frame) {
+ if (!support_->SubmitCompositorFrame(local_surface_id, std::move(frame))) {
+ compositor_frame_sink_binding_.Close();
+ OnClientConnectionLost();
+ }
+}
+
+void GpuRootCompositorFrameSink::DidNotProduceFrame(
+ const cc::BeginFrameAck& begin_frame_ack) {
+ support_->DidNotProduceFrame(begin_frame_ack);
+}
+
+void GpuRootCompositorFrameSink::ClaimTemporaryReference(
+ const SurfaceId& surface_id) {
+ support_->ClaimTemporaryReference(surface_id);
+}
+
+void GpuRootCompositorFrameSink::RequestCopyOfSurface(
+ std::unique_ptr<cc::CopyOutputRequest> request) {
+ support_->RequestCopyOfSurface(std::move(request));
+}
+
+void GpuRootCompositorFrameSink::DisplayOutputSurfaceLost() {
+ // TODO(staraz): Implement this. Client should hear about context/output
+ // surface lost.
+}
+
+void GpuRootCompositorFrameSink::DisplayWillDrawAndSwap(
+ bool will_draw_and_swap,
+ const cc::RenderPassList& render_pass) {
+ hit_test_aggregator_.PostTaskAggregate(display_->CurrentSurfaceId());
+}
+
+void GpuRootCompositorFrameSink::DisplayDidDrawAndSwap() {}
+
+void GpuRootCompositorFrameSink::DidReceiveCompositorFrameAck(
+ const std::vector<cc::ReturnedResource>& resources) {
+ if (client_)
+ client_->DidReceiveCompositorFrameAck(resources);
+}
+
+void GpuRootCompositorFrameSink::OnBeginFrame(const cc::BeginFrameArgs& args) {
+ hit_test_aggregator_.Swap();
+ if (client_)
+ client_->OnBeginFrame(args);
+}
+
+void GpuRootCompositorFrameSink::OnBeginFramePausedChanged(bool paused) {
+ if (client_)
+ client_->OnBeginFramePausedChanged(paused);
+}
+
+void GpuRootCompositorFrameSink::ReclaimResources(
+ const std::vector<cc::ReturnedResource>& resources) {
+ if (client_)
+ client_->ReclaimResources(resources);
+}
+
+void GpuRootCompositorFrameSink::WillDrawSurface(
+ const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) {}
+
+void GpuRootCompositorFrameSink::OnClientConnectionLost() {
+ support_->frame_sink_manager()->OnClientConnectionLost(
+ support_->frame_sink_id());
+}
+
+void GpuRootCompositorFrameSink::OnPrivateConnectionLost() {
+ support_->frame_sink_manager()->OnPrivateConnectionLost(
+ support_->frame_sink_id());
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.h b/chromium/components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.h
new file mode 100644
index 00000000000..05ef8d6cec0
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/gpu_root_compositor_frame_sink.h
@@ -0,0 +1,108 @@
+// 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_FRAME_SINKS_GPU_ROOT_COMPOSITOR_FRAME_SINK_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_GPU_ROOT_COMPOSITOR_FRAME_SINK_H_
+
+#include <memory>
+
+#include "cc/ipc/compositor_frame_sink.mojom.h"
+#include "cc/ipc/frame_sink_manager.mojom.h"
+#include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/service/display/display_client.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support_client.h"
+#include "components/viz/service/hit_test/hit_test_aggregator.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace cc {
+class BeginFrameSource;
+}
+
+namespace viz {
+class CompositorFrameSinkSupport;
+class Display;
+class FrameSinkManagerImpl;
+
+class GpuRootCompositorFrameSink
+ : public NON_EXPORTED_BASE(CompositorFrameSinkSupportClient),
+ public NON_EXPORTED_BASE(cc::mojom::CompositorFrameSink),
+ public NON_EXPORTED_BASE(cc::mojom::CompositorFrameSinkPrivate),
+ public NON_EXPORTED_BASE(cc::mojom::DisplayPrivate),
+ public NON_EXPORTED_BASE(DisplayClient) {
+ public:
+ GpuRootCompositorFrameSink(
+ FrameSinkManagerImpl* frame_sink_manager,
+ const FrameSinkId& frame_sink_id,
+ std::unique_ptr<Display> display,
+ std::unique_ptr<cc::BeginFrameSource> begin_frame_source,
+ cc::mojom::CompositorFrameSinkAssociatedRequest request,
+ cc::mojom::CompositorFrameSinkPrivateRequest private_request,
+ cc::mojom::CompositorFrameSinkClientPtr client,
+ cc::mojom::DisplayPrivateAssociatedRequest display_private_request);
+
+ ~GpuRootCompositorFrameSink() override;
+
+ // cc::mojom::DisplayPrivate:
+ void SetDisplayVisible(bool visible) override;
+ void ResizeDisplay(const gfx::Size& size) override;
+ void SetDisplayColorSpace(const gfx::ColorSpace& color_space) override;
+ void SetOutputIsSecure(bool secure) override;
+ void SetLocalSurfaceId(const LocalSurfaceId& local_surface_id,
+ float scale_factor) override;
+
+ // cc::mojom::CompositorFrameSink:
+ void SetNeedsBeginFrame(bool needs_begin_frame) override;
+ void SubmitCompositorFrame(const LocalSurfaceId& local_surface_id,
+ cc::CompositorFrame frame) override;
+ void DidNotProduceFrame(const cc::BeginFrameAck& begin_frame_ack) override;
+
+ // cc::mojom::CompositorFrameSinkPrivate:
+ void ClaimTemporaryReference(const SurfaceId& surface_id) override;
+ void RequestCopyOfSurface(
+ std::unique_ptr<cc::CopyOutputRequest> request) override;
+
+ private:
+ // DisplayClient:
+ void DisplayOutputSurfaceLost() override;
+ void DisplayWillDrawAndSwap(bool will_draw_and_swap,
+ const cc::RenderPassList& render_passes) override;
+ void DisplayDidDrawAndSwap() override;
+
+ // CompositorFrameSinkSupportClient:
+ void DidReceiveCompositorFrameAck(
+ const std::vector<cc::ReturnedResource>& resources) override;
+ void OnBeginFrame(const cc::BeginFrameArgs& args) override;
+ void OnBeginFramePausedChanged(bool paused) override;
+ void ReclaimResources(
+ const std::vector<cc::ReturnedResource>& resources) override;
+ void WillDrawSurface(const LocalSurfaceId& local_surface_id,
+ const gfx::Rect& damage_rect) override;
+
+ void OnClientConnectionLost();
+ void OnPrivateConnectionLost();
+
+ std::unique_ptr<CompositorFrameSinkSupport> support_;
+
+ // GpuRootCompositorFrameSink holds a Display and its BeginFrameSource if
+ // it was created with a non-null gpu::SurfaceHandle.
+ std::unique_ptr<cc::BeginFrameSource> display_begin_frame_source_;
+ std::unique_ptr<Display> display_;
+
+ cc::mojom::CompositorFrameSinkClientPtr client_;
+ mojo::AssociatedBinding<cc::mojom::CompositorFrameSink>
+ compositor_frame_sink_binding_;
+ mojo::Binding<cc::mojom::CompositorFrameSinkPrivate>
+ compositor_frame_sink_private_binding_;
+ mojo::AssociatedBinding<cc::mojom::DisplayPrivate> display_private_binding_;
+
+ HitTestAggregator hit_test_aggregator_;
+
+ DISALLOW_COPY_AND_ASSIGN(GpuRootCompositorFrameSink);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_GPU_ROOT_COMPOSITOR_FRAME_SINK_H_
diff --git a/chromium/components/viz/service/frame_sinks/primary_begin_frame_source.cc b/chromium/components/viz/service/frame_sinks/primary_begin_frame_source.cc
new file mode 100644
index 00000000000..58ec08404e0
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/primary_begin_frame_source.cc
@@ -0,0 +1,89 @@
+// 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/frame_sinks/primary_begin_frame_source.h"
+
+namespace viz {
+
+PrimaryBeginFrameSource::PrimaryBeginFrameSource()
+ : begin_frame_source_(this) {}
+
+PrimaryBeginFrameSource::~PrimaryBeginFrameSource() = default;
+
+void PrimaryBeginFrameSource::OnBeginFrameSourceAdded(
+ cc::BeginFrameSource* begin_frame_source) {
+ sources_.insert(begin_frame_source);
+
+ if (current_begin_frame_source_)
+ return;
+
+ current_begin_frame_source_ = begin_frame_source;
+ if (needs_begin_frames_ && current_begin_frame_source_)
+ current_begin_frame_source_->AddObserver(this);
+}
+
+void PrimaryBeginFrameSource::OnBeginFrameSourceRemoved(
+ cc::BeginFrameSource* begin_frame_source) {
+ sources_.erase(begin_frame_source);
+ if (current_begin_frame_source_ != begin_frame_source)
+ return;
+
+ if (needs_begin_frames_)
+ current_begin_frame_source_->RemoveObserver(this);
+
+ if (!sources_.empty())
+ current_begin_frame_source_ = *sources_.begin();
+ else
+ current_begin_frame_source_ = nullptr;
+
+ if (needs_begin_frames_ && current_begin_frame_source_)
+ current_begin_frame_source_->AddObserver(this);
+}
+
+void PrimaryBeginFrameSource::OnBeginFrame(const cc::BeginFrameArgs& args) {
+ begin_frame_source_.OnBeginFrame(args);
+ last_begin_frame_args_ = args;
+}
+
+const cc::BeginFrameArgs& PrimaryBeginFrameSource::LastUsedBeginFrameArgs()
+ const {
+ return last_begin_frame_args_;
+}
+
+void PrimaryBeginFrameSource::OnBeginFrameSourcePausedChanged(bool paused) {}
+
+void PrimaryBeginFrameSource::DidFinishFrame(cc::BeginFrameObserver* obs) {
+ begin_frame_source_.DidFinishFrame(obs);
+}
+
+void PrimaryBeginFrameSource::AddObserver(cc::BeginFrameObserver* obs) {
+ begin_frame_source_.AddObserver(obs);
+}
+
+void PrimaryBeginFrameSource::RemoveObserver(cc::BeginFrameObserver* obs) {
+ begin_frame_source_.RemoveObserver(obs);
+}
+
+bool PrimaryBeginFrameSource::IsThrottled() const {
+ return current_begin_frame_source_
+ ? current_begin_frame_source_->IsThrottled()
+ : true;
+}
+
+void PrimaryBeginFrameSource::OnNeedsBeginFrames(bool needs_begin_frames) {
+ if (needs_begin_frames_ == needs_begin_frames)
+ return;
+
+ needs_begin_frames_ = needs_begin_frames;
+
+ if (!current_begin_frame_source_)
+ return;
+
+ if (needs_begin_frames_)
+ current_begin_frame_source_->AddObserver(this);
+ else
+ current_begin_frame_source_->RemoveObserver(this);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/primary_begin_frame_source.h b/chromium/components/viz/service/frame_sinks/primary_begin_frame_source.h
new file mode 100644
index 00000000000..51aec3dc8cb
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/primary_begin_frame_source.h
@@ -0,0 +1,58 @@
+// 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_FRAME_SINKS_PRIMARY_BEGIN_FRAME_SOURCE_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_PRIMARY_BEGIN_FRAME_SOURCE_H_
+
+#include "base/containers/flat_set.h"
+#include "cc/scheduler/begin_frame_source.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+// PrimaryBeginFrameSource echos the first BeginFrameSource in the system.
+// If the first source goes away then it will echo the new first
+// BeginFrameSource.
+class VIZ_SERVICE_EXPORT PrimaryBeginFrameSource
+ : public cc::BeginFrameSource,
+ public cc::BeginFrameObserver,
+ public cc::ExternalBeginFrameSourceClient {
+ public:
+ PrimaryBeginFrameSource();
+ ~PrimaryBeginFrameSource() override;
+
+ void OnBeginFrameSourceAdded(cc::BeginFrameSource* begin_frame_source);
+ void OnBeginFrameSourceRemoved(cc::BeginFrameSource* begin_frame_source);
+
+ // cc::BeginFrameObserver implementation.
+ void OnBeginFrame(const cc::BeginFrameArgs& args) override;
+ const cc::BeginFrameArgs& LastUsedBeginFrameArgs() const override;
+ void OnBeginFrameSourcePausedChanged(bool paused) override;
+
+ // cc::BeginFrameSource implementation.
+ void DidFinishFrame(cc::BeginFrameObserver* obs) override;
+ void AddObserver(cc::BeginFrameObserver* obs) override;
+ void RemoveObserver(cc::BeginFrameObserver* obs) override;
+ bool IsThrottled() const override;
+
+ // cc::ExternalBeginFrameSourceClient implementation.
+ void OnNeedsBeginFrames(bool needs_begin_frames) override;
+
+ private:
+ cc::ExternalBeginFrameSource begin_frame_source_;
+ cc::BeginFrameSource* current_begin_frame_source_ = nullptr;
+
+ // The last begin frame args generated by the begin frame source.
+ cc::BeginFrameArgs last_begin_frame_args_;
+
+ bool needs_begin_frames_ = false;
+
+ base::flat_set<cc::BeginFrameSource*> sources_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrimaryBeginFrameSource);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_PRIMARY_BEGIN_FRAME_SOURCE_H_
diff --git a/chromium/components/viz/service/frame_sinks/referenced_surface_tracker.cc b/chromium/components/viz/service/frame_sinks/referenced_surface_tracker.cc
new file mode 100644
index 00000000000..6464e324d1c
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/referenced_surface_tracker.cc
@@ -0,0 +1,38 @@
+// Copyright 2016 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/frame_sinks/referenced_surface_tracker.h"
+
+#include "base/logging.h"
+
+namespace viz {
+
+void GetSurfaceReferenceDifference(
+ const SurfaceId& parent_surface_id,
+ const base::flat_set<SurfaceId>& old_referenced_surfaces,
+ const base::flat_set<SurfaceId>& new_referenced_surfaces,
+ std::vector<cc::SurfaceReference>* references_to_add,
+ std::vector<cc::SurfaceReference>* references_to_remove) {
+ DCHECK(parent_surface_id.is_valid());
+
+ // Find SurfaceIds in |old_referenced_surfaces| that aren't referenced
+ // anymore.
+ for (const SurfaceId& surface_id : old_referenced_surfaces) {
+ if (new_referenced_surfaces.count(surface_id) == 0) {
+ references_to_remove->push_back(
+ cc::SurfaceReference(parent_surface_id, surface_id));
+ }
+ }
+
+ // Find SurfaceIds in |new_referenced_surfaces| that aren't already
+ // referenced.
+ for (const SurfaceId& surface_id : new_referenced_surfaces) {
+ if (old_referenced_surfaces.count(surface_id) == 0) {
+ references_to_add->push_back(
+ cc::SurfaceReference(parent_surface_id, surface_id));
+ }
+ }
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/referenced_surface_tracker.h b/chromium/components/viz/service/frame_sinks/referenced_surface_tracker.h
new file mode 100644
index 00000000000..4bed2beb346
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/referenced_surface_tracker.h
@@ -0,0 +1,30 @@
+// Copyright 2016 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_FRAME_SINKS_REFERENCED_SURFACE_TRACKER_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_REFERENCED_SURFACE_TRACKER_H_
+
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "cc/surfaces/surface_reference.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+// Finds the difference between |old_referenced_surfaces| and
+// |new_referenced_surfaces|. Populates |references_to_add| and
+// |references_to_remove| based on the difference using |parent_surface_id| as
+// the parent for references.
+void VIZ_SERVICE_EXPORT GetSurfaceReferenceDifference(
+ const SurfaceId& parent_surface_id,
+ const base::flat_set<SurfaceId>& old_referenced_surfaces,
+ const base::flat_set<SurfaceId>& new_referenced_surfaces,
+ std::vector<cc::SurfaceReference>* references_to_add,
+ std::vector<cc::SurfaceReference>* references_to_remove);
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_REFERENCED_SURFACE_TRACKER_H_
diff --git a/chromium/components/viz/service/frame_sinks/referenced_surface_tracker_unittest.cc b/chromium/components/viz/service/frame_sinks/referenced_surface_tracker_unittest.cc
new file mode 100644
index 00000000000..7ff2d17d26a
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/referenced_surface_tracker_unittest.cc
@@ -0,0 +1,147 @@
+// 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/frame_sinks/referenced_surface_tracker.h"
+
+#include <memory>
+
+#include "base/containers/flat_set.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "cc/surfaces/surface_reference.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::UnorderedElementsAre;
+using testing::IsEmpty;
+
+namespace viz {
+namespace test {
+namespace {
+
+constexpr FrameSinkId kParentFrameSink(2, 1);
+constexpr FrameSinkId kChildFrameSink1(65563, 1);
+constexpr FrameSinkId kChildFrameSink2(65564, 1);
+
+base::flat_set<SurfaceId> MakeReferenceSet(
+ std::initializer_list<SurfaceId> surface_ids) {
+ return base::flat_set<SurfaceId>(surface_ids, base::KEEP_FIRST_OF_DUPES);
+}
+
+SurfaceId MakeSurfaceId(const FrameSinkId& frame_sink_id, uint32_t local_id) {
+ return SurfaceId(
+ frame_sink_id,
+ LocalSurfaceId(local_id, base::UnguessableToken::Deserialize(0, 1u)));
+}
+
+} // namespace
+
+class ReferencedSurfaceTrackerTest : public testing::Test {
+ public:
+ ReferencedSurfaceTrackerTest() {}
+ ~ReferencedSurfaceTrackerTest() override {}
+
+ const std::vector<cc::SurfaceReference>& references_to_remove() const {
+ return references_to_remove_;
+ }
+
+ const std::vector<cc::SurfaceReference>& references_to_add() const {
+ return references_to_add_;
+ }
+
+ void UpdateReferences(
+ const SurfaceId& surface_id,
+ const base::flat_set<SurfaceId>& old_referenced_surfaces,
+ const base::flat_set<SurfaceId>& new_referenced_surfaces) {
+ references_to_add_.clear();
+ references_to_remove_.clear();
+ GetSurfaceReferenceDifference(surface_id, old_referenced_surfaces,
+ new_referenced_surfaces, &references_to_add_,
+ &references_to_remove_);
+ }
+
+ private:
+ std::vector<cc::SurfaceReference> references_to_add_;
+ std::vector<cc::SurfaceReference> references_to_remove_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReferencedSurfaceTrackerTest);
+};
+
+TEST_F(ReferencedSurfaceTrackerTest, AddSurfaceReference) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const cc::SurfaceReference reference(parent_id, child_id1);
+
+ // Check that reference to |child_id1| is added.
+ UpdateReferences(parent_id, MakeReferenceSet({}),
+ MakeReferenceSet({child_id1}));
+ EXPECT_THAT(references_to_add(), UnorderedElementsAre(reference));
+ EXPECT_THAT(references_to_remove(), IsEmpty());
+}
+
+TEST_F(ReferencedSurfaceTrackerTest, NoChangeToReferences) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const cc::SurfaceReference reference(parent_id, child_id1);
+
+ // Check that no references are added or removed.
+ auto referenced_surfaces = MakeReferenceSet({child_id1});
+ UpdateReferences(parent_id, referenced_surfaces, referenced_surfaces);
+ EXPECT_THAT(references_to_remove(), IsEmpty());
+ EXPECT_THAT(references_to_add(), IsEmpty());
+}
+
+TEST_F(ReferencedSurfaceTrackerTest, RemoveSurfaceReference) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const cc::SurfaceReference reference(parent_id, child_id1);
+
+ // Check that reference to |child_id1| is removed.
+ UpdateReferences(parent_id, MakeReferenceSet({child_id1}),
+ MakeReferenceSet({}));
+ EXPECT_THAT(references_to_add(), IsEmpty());
+ EXPECT_THAT(references_to_remove(), UnorderedElementsAre(reference));
+}
+
+TEST_F(ReferencedSurfaceTrackerTest, RemoveOneOfTwoSurfaceReferences) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1_first = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id1_second = MakeSurfaceId(kChildFrameSink1, 2);
+ const cc::SurfaceReference reference_first(parent_id, child_id1_first);
+ const cc::SurfaceReference reference_second(parent_id, child_id1_second);
+
+ // Check that reference to |child_id1_first| is removed and reference to
+ // |child_id1_second| is added.
+ UpdateReferences(parent_id, MakeReferenceSet({child_id1_first}),
+ MakeReferenceSet({child_id1_second}));
+ EXPECT_THAT(references_to_remove(), UnorderedElementsAre(reference_first));
+ EXPECT_THAT(references_to_add(), UnorderedElementsAre(reference_second));
+}
+
+TEST_F(ReferencedSurfaceTrackerTest, AddTwoThenRemoveOneSurfaceReferences) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 2);
+ const cc::SurfaceReference reference1(parent_id, child_id1);
+ const cc::SurfaceReference reference2(parent_id, child_id2);
+
+ // Check that first frame adds both surface references.
+ const auto initial_referenced = MakeReferenceSet({child_id1, child_id2});
+ UpdateReferences(parent_id, MakeReferenceSet({}), initial_referenced);
+ EXPECT_THAT(references_to_remove(), IsEmpty());
+ EXPECT_THAT(references_to_add(),
+ UnorderedElementsAre(reference1, reference2));
+
+ // Check that reference to |child_id1| is removed but not to |child_id2|.
+ UpdateReferences(parent_id, initial_referenced,
+ MakeReferenceSet({child_id2}));
+ EXPECT_THAT(references_to_remove(), UnorderedElementsAre(reference1));
+ EXPECT_THAT(references_to_add(), IsEmpty());
+}
+
+} // namespace test
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/surface_references_unittest.cc b/chromium/components/viz/service/frame_sinks/surface_references_unittest.cc
new file mode 100644
index 00000000000..d0269dcc90d
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/surface_references_unittest.cc
@@ -0,0 +1,527 @@
+// Copyright 2016 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 <stddef.h>
+
+#include <unordered_map>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/memory/ptr_util.h"
+#include "cc/surfaces/surface.h"
+#include "cc/surfaces/surface_manager.h"
+#include "cc/test/compositor_frame_helpers.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+using testing::SizeIs;
+using testing::UnorderedElementsAre;
+
+namespace viz {
+namespace test {
+namespace {
+
+constexpr FrameSinkId kFrameSink1(1, 0);
+constexpr FrameSinkId kFrameSink2(2, 0);
+constexpr FrameSinkId kFrameSink3(3, 0);
+
+} // namespace
+
+// Tests for reference tracking in CompositorFrameSinkSupport and
+// cc::SurfaceManager.
+class SurfaceReferencesTest : public testing::Test {
+ public:
+ cc::SurfaceManager& GetSurfaceManager() {
+ return *manager_->surface_manager();
+ }
+
+ // Creates a new Surface with the provided |frame_sink_id| and |local_id|.
+ // Will first create a Surfacesupport for |frame_sink_id| if necessary.
+ SurfaceId CreateSurface(const FrameSinkId& frame_sink_id, uint32_t local_id) {
+ LocalSurfaceId local_surface_id(local_id,
+ base::UnguessableToken::Deserialize(0, 1u));
+ GetCompositorFrameSinkSupport(frame_sink_id)
+ .SubmitCompositorFrame(local_surface_id,
+ cc::test::MakeCompositorFrame());
+ return SurfaceId(frame_sink_id, local_surface_id);
+ }
+
+ // Destroy Surface with |surface_id|.
+ void DestroySurface(const SurfaceId& surface_id) {
+ GetCompositorFrameSinkSupport(surface_id.frame_sink_id())
+ .EvictCurrentSurface();
+ }
+
+ CompositorFrameSinkSupport& GetCompositorFrameSinkSupport(
+ const FrameSinkId& frame_sink_id) {
+ auto& support_ptr = supports_[frame_sink_id];
+ if (!support_ptr) {
+ constexpr bool is_root = false;
+ constexpr bool handles_frame_sink_id_invalidation = true;
+ constexpr bool needs_sync_points = true;
+ support_ptr = CompositorFrameSinkSupport::Create(
+ nullptr, manager_.get(), frame_sink_id, is_root,
+ handles_frame_sink_id_invalidation, needs_sync_points);
+ }
+ return *support_ptr;
+ }
+
+ void DestroyCompositorFrameSinkSupport(const FrameSinkId& frame_sink_id) {
+ auto support_ptr = supports_.find(frame_sink_id);
+ ASSERT_NE(support_ptr, supports_.end());
+ supports_.erase(support_ptr);
+ }
+
+ void RemoveSurfaceReference(const SurfaceId& parent_id,
+ const SurfaceId& child_id) {
+ manager_->surface_manager()->RemoveSurfaceReferences(
+ {cc::SurfaceReference(parent_id, child_id)});
+ }
+
+ void AddSurfaceReference(const SurfaceId& parent_id,
+ const SurfaceId& child_id) {
+ manager_->surface_manager()->AddSurfaceReferences(
+ {cc::SurfaceReference(parent_id, child_id)});
+ }
+
+ // Returns all the references where |surface_id| is the parent.
+ const base::flat_set<SurfaceId>& GetReferencesFrom(
+ const SurfaceId& surface_id) {
+ return GetSurfaceManager().GetSurfacesReferencedByParent(surface_id);
+ }
+
+ // Returns all the references where |surface_id| is the child.
+ const base::flat_set<SurfaceId>& GetReferencesFor(
+ const SurfaceId& surface_id) {
+ return GetSurfaceManager().GetSurfacesThatReferenceChild(surface_id);
+ }
+
+ // Temporary references are stored as a map in cc::SurfaceManager. This method
+ // converts the map to a vector.
+ std::vector<SurfaceId> GetAllTempReferences() {
+ std::vector<SurfaceId> temp_references;
+ for (auto& map_entry : GetSurfaceManager().temporary_references_)
+ temp_references.push_back(map_entry.first);
+ return temp_references;
+ }
+
+ protected:
+ // testing::Test:
+ void SetUp() override {
+ // Start each test with a fresh cc::SurfaceManager instance.
+ manager_ = base::MakeUnique<FrameSinkManagerImpl>(
+ nullptr /* display_provider */,
+ cc::SurfaceManager::LifetimeType::REFERENCES);
+ }
+ void TearDown() override {
+ for (auto& support : supports_)
+ support.second->EvictCurrentSurface();
+ supports_.clear();
+ manager_.reset();
+ }
+
+ std::unordered_map<FrameSinkId,
+ std::unique_ptr<CompositorFrameSinkSupport>,
+ FrameSinkIdHash>
+ supports_;
+ std::unique_ptr<FrameSinkManagerImpl> manager_;
+};
+
+TEST_F(SurfaceReferencesTest, AddReference) {
+ SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+
+ EXPECT_THAT(GetReferencesFor(id1),
+ UnorderedElementsAre(GetSurfaceManager().GetRootSurfaceId()));
+ EXPECT_THAT(GetReferencesFrom(id1), IsEmpty());
+}
+
+TEST_F(SurfaceReferencesTest, AddRemoveReference) {
+ SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ SurfaceId id2 = CreateSurface(kFrameSink2, 1);
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+ AddSurfaceReference(id1, id2);
+
+ EXPECT_THAT(GetReferencesFor(id1),
+ UnorderedElementsAre(GetSurfaceManager().GetRootSurfaceId()));
+ EXPECT_THAT(GetReferencesFor(id2), UnorderedElementsAre(id1));
+ EXPECT_THAT(GetReferencesFrom(id1), UnorderedElementsAre(id2));
+ EXPECT_THAT(GetReferencesFrom(id2), IsEmpty());
+
+ RemoveSurfaceReference(id1, id2);
+ EXPECT_THAT(GetReferencesFor(id1), SizeIs(1));
+ EXPECT_THAT(GetReferencesFor(id2), IsEmpty());
+ EXPECT_THAT(GetReferencesFrom(id1), IsEmpty());
+ EXPECT_THAT(GetReferencesFrom(id2), IsEmpty());
+}
+
+TEST_F(SurfaceReferencesTest, NewSurfaceFromFrameSink) {
+ SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ SurfaceId id2 = CreateSurface(kFrameSink2, 1);
+ SurfaceId id3 = CreateSurface(kFrameSink3, 1);
+
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+ AddSurfaceReference(id1, id2);
+ AddSurfaceReference(id2, id3);
+
+ // |kFramesink2| received a CompositorFrame with a new size, so it destroys
+ // |id2| and creates |id2_next|. No reference have been removed yet.
+ SurfaceId id2_next = CreateSurface(kFrameSink2, 2);
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id2_next));
+
+ // Add references to and from |id2_next|.
+ AddSurfaceReference(id1, id2_next);
+ AddSurfaceReference(id2_next, id3);
+ EXPECT_THAT(GetReferencesFor(id2), UnorderedElementsAre(id1));
+ EXPECT_THAT(GetReferencesFor(id2_next), UnorderedElementsAre(id1));
+ EXPECT_THAT(GetReferencesFor(id3), UnorderedElementsAre(id2, id2_next));
+
+ RemoveSurfaceReference(id1, id2);
+ EXPECT_THAT(GetReferencesFor(id2), IsEmpty());
+ EXPECT_THAT(GetReferencesFor(id2_next), UnorderedElementsAre(id1));
+ EXPECT_THAT(GetReferencesFor(id3), UnorderedElementsAre(id2_next));
+
+ // |id2| should be deleted during GC but other surfaces shouldn't.
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id2_next));
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id3));
+}
+
+TEST_F(SurfaceReferencesTest, ReferenceCycleGetsDeleted) {
+ SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ SurfaceId id2 = CreateSurface(kFrameSink2, 1);
+ SurfaceId id3 = CreateSurface(kFrameSink3, 1);
+
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+ AddSurfaceReference(id1, id2);
+ AddSurfaceReference(id2, id3);
+
+ // This reference forms a cycle between id2 and id3.
+ AddSurfaceReference(id3, id2);
+
+ DestroySurface(id3);
+ DestroySurface(id2);
+ DestroySurface(id1);
+
+ RemoveSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+
+ // Removing the reference from the root to id1 should allow all three surfaces
+ // to be deleted during GC even with a cycle between 2 and 3.
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id1));
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id3));
+}
+
+TEST_F(SurfaceReferencesTest, SurfacesAreDeletedDuringGarbageCollection) {
+ SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ SurfaceId id2 = CreateSurface(kFrameSink2, 1);
+
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+ AddSurfaceReference(id1, id2);
+
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id1));
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+
+ // Destroying the surfaces shouldn't delete them yet, since there is still an
+ // active reference on all surfaces.
+ DestroySurface(id1);
+ DestroySurface(id2);
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id1));
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+
+ // Should delete |id2| when the only reference to it is removed.
+ RemoveSurfaceReference(id1, id2);
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+
+ // Should delete |id1| when the only reference to it is removed.
+ RemoveSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id1));
+}
+
+TEST_F(SurfaceReferencesTest, GarbageCollectionWorksRecusively) {
+ SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ SurfaceId id2 = CreateSurface(kFrameSink2, 1);
+ SurfaceId id3 = CreateSurface(kFrameSink3, 1);
+
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+ AddSurfaceReference(id1, id2);
+ AddSurfaceReference(id2, id3);
+
+ DestroySurface(id3);
+ DestroySurface(id2);
+ DestroySurface(id1);
+
+ // Destroying the surfaces shouldn't delete them yet, since there is still an
+ // active reference on all surfaces.
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id3));
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id1));
+
+ RemoveSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+
+ // Removing the reference from the root to id1 should allow all three surfaces
+ // to be deleted during GC.
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id1));
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id3));
+}
+
+TEST_F(SurfaceReferencesTest, TryAddReferenceSameReferenceTwice) {
+ SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ SurfaceId id2 = CreateSurface(kFrameSink2, 1);
+
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+ AddSurfaceReference(id1, id2);
+ EXPECT_THAT(GetReferencesFor(id2), SizeIs(1));
+ EXPECT_THAT(GetReferencesFrom(id1), SizeIs(1));
+
+ // The second request should be ignored without crashing.
+ AddSurfaceReference(id1, id2);
+ EXPECT_THAT(GetReferencesFor(id2), SizeIs(1));
+ EXPECT_THAT(GetReferencesFrom(id1), SizeIs(1));
+}
+
+TEST_F(SurfaceReferencesTest, AddingSelfReferenceFails) {
+ SurfaceId id1 = CreateSurface(kFrameSink2, 1);
+
+ // A temporary reference must exist to |id1|.
+ EXPECT_THAT(GetAllTempReferences(), ElementsAre(id1));
+ EXPECT_THAT(GetReferencesFrom(id1), IsEmpty());
+ EXPECT_THAT(GetReferencesFor(id1), IsEmpty());
+
+ // Try to add a self reference. This should fail.
+ AddSurfaceReference(id1, id1);
+
+ // Adding a self reference should be ignored without crashing. The temporary
+ // reference to |id1| must still exist.
+ EXPECT_THAT(GetAllTempReferences(), ElementsAre(id1));
+ EXPECT_THAT(GetReferencesFrom(id1), IsEmpty());
+ EXPECT_THAT(GetReferencesFor(id1), IsEmpty());
+}
+
+TEST_F(SurfaceReferencesTest, RemovingNonexistantReferenceFails) {
+ SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ SurfaceId id2 = CreateSurface(kFrameSink2, 1);
+
+ // Removing non-existent reference should be ignored.
+ AddSurfaceReference(id1, id2);
+ RemoveSurfaceReference(id2, id1);
+ EXPECT_THAT(GetReferencesFrom(id1), SizeIs(1));
+ EXPECT_THAT(GetReferencesFor(id2), SizeIs(1));
+}
+
+TEST_F(SurfaceReferencesTest, AddSurfaceThenReference) {
+ // Create a new surface.
+ const SurfaceId surface_id = CreateSurface(kFrameSink2, 1);
+
+ // A temporary reference must be added to |surface_id|.
+ EXPECT_THAT(GetAllTempReferences(), ElementsAre(surface_id));
+
+ // Create |parent_id| and add a real reference from it to |surface_id|.
+ const SurfaceId parent_id = CreateSurface(kFrameSink1, 1);
+ AddSurfaceReference(parent_id, surface_id);
+
+ // The temporary reference to |surface_id| should be gone.
+ // The only temporary reference should be to |parent_id|.
+ // There must be a real reference from |parent_id| to |child_id|.
+ EXPECT_THAT(GetAllTempReferences(), ElementsAre(parent_id));
+ EXPECT_THAT(GetReferencesFrom(parent_id), ElementsAre(surface_id));
+}
+
+TEST_F(SurfaceReferencesTest, AddSurfaceThenRootReference) {
+ // Create a new surface.
+ const SurfaceId surface_id = CreateSurface(kFrameSink1, 1);
+
+ // Temporary reference should be added to |surface_id|.
+ EXPECT_THAT(GetAllTempReferences(), ElementsAre(surface_id));
+
+ // Add a real reference from root to |surface_id|.
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), surface_id);
+
+ // The temporary reference should be gone and there should now be a surface
+ // reference from root to |surface_id|.
+ EXPECT_TRUE(GetAllTempReferences().empty());
+ EXPECT_THAT(GetReferencesFrom(GetSurfaceManager().GetRootSurfaceId()),
+ ElementsAre(surface_id));
+}
+
+TEST_F(SurfaceReferencesTest, AddTwoSurfacesThenOneReference) {
+ // Create two surfaces with different FrameSinkIds.
+ const SurfaceId surface_id1 = CreateSurface(kFrameSink2, 1);
+ const SurfaceId surface_id2 = CreateSurface(kFrameSink3, 1);
+
+ // Temporary reference should be added for both surfaces.
+ EXPECT_THAT(GetAllTempReferences(),
+ UnorderedElementsAre(surface_id1, surface_id2));
+
+ // Create |parent_id| and add a real reference from it to |surface_id1|.
+ const SurfaceId parent_id = CreateSurface(kFrameSink1, 1);
+ AddSurfaceReference(parent_id, surface_id1);
+
+ // Real reference must be added to |surface_id1| and the temporary reference
+ // to it must be gone.
+ // There should still be a temporary reference left to |surface_id2|.
+ // A temporary reference to |parent_id| must be created.
+ EXPECT_THAT(GetAllTempReferences(),
+ UnorderedElementsAre(parent_id, surface_id2));
+ EXPECT_THAT(GetReferencesFrom(parent_id), ElementsAre(surface_id1));
+}
+
+TEST_F(SurfaceReferencesTest, AddSurfacesSkipReference) {
+ // Add two surfaces that have the same FrameSinkId. This would happen
+ // when a client submits two CompositorFrames before parent submits a new
+ // CompositorFrame.
+ const SurfaceId surface_id1 = CreateSurface(kFrameSink2, 2);
+ const SurfaceId surface_id2 = CreateSurface(kFrameSink2, 1);
+
+ // Temporary references should be added for both surfaces and they should be
+ // stored in the order of creation.
+ EXPECT_THAT(GetAllTempReferences(),
+ UnorderedElementsAre(surface_id1, surface_id2));
+
+ // Create |parent_id| and add a reference from it to |surface_id2| which was
+ // created later.
+ const SurfaceId parent_id = CreateSurface(kFrameSink1, 1);
+ AddSurfaceReference(parent_id, surface_id2);
+
+ // The real reference should be added for |surface_id2| and the temporary
+ // references to both |surface_id1| and |surface_id2| should be gone.
+ // There should be a temporary reference to |parent_id|.
+ EXPECT_THAT(GetAllTempReferences(), ElementsAre(parent_id));
+ EXPECT_THAT(GetReferencesFrom(parent_id), ElementsAre(surface_id2));
+}
+
+TEST_F(SurfaceReferencesTest, RemoveFirstTempReferenceOnly) {
+ // Add two surfaces that have the same FrameSinkId. This would happen
+ // when a client submits two CFs before parent submits a new CF.
+ const SurfaceId surface_id1 = CreateSurface(kFrameSink2, 1);
+ const SurfaceId surface_id2 = CreateSurface(kFrameSink2, 2);
+
+ // Temporary references should be added for both surfaces and they should be
+ // stored in the order of creation.
+ EXPECT_THAT(GetAllTempReferences(),
+ UnorderedElementsAre(surface_id1, surface_id2));
+
+ // Create |parent_id| and add a reference from it to |surface_id1| which was
+ // created earlier.
+ const SurfaceId parent_id = CreateSurface(kFrameSink1, 1);
+ AddSurfaceReference(parent_id, surface_id1);
+
+ // The real reference should be added for |surface_id1| and its temporary
+ // reference should be removed. The temporary reference for |surface_id2|
+ // should remain. A temporary reference must be added for |parent_id|.
+ EXPECT_THAT(GetAllTempReferences(),
+ UnorderedElementsAre(parent_id, surface_id2));
+ EXPECT_THAT(GetReferencesFrom(parent_id), ElementsAre(surface_id1));
+}
+
+TEST_F(SurfaceReferencesTest, SurfaceWithTemporaryReferenceIsNotDeleted) {
+ const SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+
+ // We create |id2| and never add a real reference to it. This leaves the
+ // temporary reference.
+ const SurfaceId id2 = CreateSurface(kFrameSink2, 1);
+ ASSERT_THAT(GetAllTempReferences(), UnorderedElementsAre(id2));
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+
+ // Destroy both surfaces so they can be garbage collected. We remove the
+ // surface reference to |id1| which will run GarbageCollectSurfaces().
+ DestroySurface(id1);
+ DestroySurface(id2);
+ RemoveSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), id1);
+
+ // |id1| is destroyed and has no references, so it's deleted.
+ EXPECT_EQ(nullptr, GetSurfaceManager().GetSurfaceForId(id1));
+
+ // |id2| is destroyed but has a temporary reference, it's not deleted.
+ EXPECT_NE(nullptr, GetSurfaceManager().GetSurfaceForId(id2));
+}
+
+// Checks that when a temporary reference is assigned an owner, if the owner is
+// invalidated then the temporary reference is also removed.
+TEST_F(SurfaceReferencesTest, InvalidateTempReferenceOwnerRemovesReference) {
+ // Surface |id1| should have a temporary reference on creation.
+ const SurfaceId id1 = CreateSurface(kFrameSink2, 1);
+ ASSERT_THAT(GetAllTempReferences(), UnorderedElementsAre(id1));
+
+ // |id1| should have a temporary reference after an owner is assigned.
+ GetSurfaceManager().AssignTemporaryReference(id1, kFrameSink1);
+ ASSERT_THAT(GetAllTempReferences(), UnorderedElementsAre(id1));
+
+ // When |kFrameSink1| is invalidated the temporary reference will be removed.
+ GetSurfaceManager().InvalidateFrameSinkId(kFrameSink1);
+ ASSERT_THAT(GetAllTempReferences(), IsEmpty());
+}
+
+// Checks that adding a surface reference clears the temporary reference and
+// ownership. Invalidating the old owner shouldn't do anything.
+TEST_F(SurfaceReferencesTest, InvalidateHasNoEffectOnSurfaceReferences) {
+ const SurfaceId parent_id = CreateSurface(kFrameSink1, 1);
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), parent_id);
+
+ const SurfaceId id1 = CreateSurface(kFrameSink2, 1);
+ GetSurfaceManager().AssignTemporaryReference(id1, kFrameSink1);
+ ASSERT_THAT(GetAllTempReferences(), UnorderedElementsAre(id1));
+
+ // Adding a real surface reference will remove the temporary reference.
+ AddSurfaceReference(parent_id, id1);
+ ASSERT_THAT(GetAllTempReferences(), IsEmpty());
+ ASSERT_THAT(GetReferencesFor(id1), UnorderedElementsAre(parent_id));
+
+ // When |kFrameSink1| is invalidated it shouldn't change the surface
+ // references.
+ DestroyCompositorFrameSinkSupport(kFrameSink1);
+ ASSERT_THAT(GetReferencesFor(id1), UnorderedElementsAre(parent_id));
+}
+
+TEST_F(SurfaceReferencesTest, CheckDropTemporaryReferenceWorks) {
+ const SurfaceId id1 = CreateSurface(kFrameSink1, 1);
+ ASSERT_THAT(GetAllTempReferences(), UnorderedElementsAre(id1));
+
+ // An example of why this could happen is the window server doesn't know the
+ // owner, maybe it has crashed and been cleanup already, and asks to drop the
+ // temporary reference.
+ GetSurfaceManager().DropTemporaryReference(id1);
+ ASSERT_THAT(GetAllTempReferences(), IsEmpty());
+}
+
+// Checks that we handle ownership and temporary references correctly when there
+// are multiple temporary references. This tests something like the parent
+// client crashing, so it's
+TEST_F(SurfaceReferencesTest, TempReferencesWithClientCrash) {
+ const SurfaceId parent_id = CreateSurface(kFrameSink1, 1);
+ AddSurfaceReference(GetSurfaceManager().GetRootSurfaceId(), parent_id);
+
+ const SurfaceId id1a = CreateSurface(kFrameSink2, 1);
+ const SurfaceId id1b = CreateSurface(kFrameSink2, 2);
+
+ ASSERT_THAT(GetAllTempReferences(), UnorderedElementsAre(id1a, id1b));
+
+ // Assign |id1a| to |kFrameSink1|. This doesn't change the temporary
+ // reference, it just assigns as owner to it.
+ GetSurfaceManager().AssignTemporaryReference(id1a, kFrameSink1);
+ ASSERT_THAT(GetAllTempReferences(), UnorderedElementsAre(id1a, id1b));
+
+ // If the parent client crashes then the FrameSink connection will be closed
+ // and the FrameSinkId invalidated. The temporary reference |kFrameSink1|
+ // owns to |id2a| will be removed.
+ DestroyCompositorFrameSinkSupport(kFrameSink1);
+ ASSERT_THAT(GetAllTempReferences(), UnorderedElementsAre(id1b));
+
+ // If the parent has crashed then the window server will have already removed
+ // it from the ServerWindow hierarchy and won't have an owner for |id2b|. The
+ // window server will ask to drop the reference instead.
+ GetSurfaceManager().DropTemporaryReference(id1b);
+ ASSERT_THAT(GetAllTempReferences(), IsEmpty());
+}
+
+} // namespace test
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/surface_resource_holder.cc b/chromium/components/viz/service/frame_sinks/surface_resource_holder.cc
new file mode 100644
index 00000000000..63d5d4e0e8a
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/surface_resource_holder.cc
@@ -0,0 +1,70 @@
+// Copyright 2014 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/frame_sinks/surface_resource_holder.h"
+
+#include "components/viz/service/frame_sinks/surface_resource_holder_client.h"
+
+namespace viz {
+
+SurfaceResourceHolder::SurfaceResourceHolder(
+ SurfaceResourceHolderClient* client)
+ : client_(client) {}
+
+SurfaceResourceHolder::~SurfaceResourceHolder() = default;
+
+SurfaceResourceHolder::ResourceRefs::ResourceRefs()
+ : refs_received_from_child(0), refs_holding_resource_alive(0) {}
+
+void SurfaceResourceHolder::Reset() {
+ resource_id_info_map_.clear();
+}
+
+void SurfaceResourceHolder::ReceiveFromChild(
+ const std::vector<cc::TransferableResource>& resources) {
+ for (const auto& resource : resources) {
+ ResourceRefs& ref = resource_id_info_map_[resource.id];
+ ref.refs_holding_resource_alive++;
+ ref.refs_received_from_child++;
+ }
+}
+
+void SurfaceResourceHolder::RefResources(
+ const std::vector<cc::TransferableResource>& resources) {
+ for (const auto& resource : resources) {
+ ResourceIdInfoMap::iterator count_it =
+ resource_id_info_map_.find(resource.id);
+ DCHECK(count_it != resource_id_info_map_.end());
+ count_it->second.refs_holding_resource_alive++;
+ }
+}
+
+void SurfaceResourceHolder::UnrefResources(
+ const std::vector<cc::ReturnedResource>& resources) {
+ std::vector<cc::ReturnedResource> resources_available_to_return;
+
+ for (const auto& resource : resources) {
+ ResourceIdInfoMap::iterator count_it =
+ resource_id_info_map_.find(resource.id);
+ if (count_it == resource_id_info_map_.end())
+ continue;
+ ResourceRefs& ref = count_it->second;
+ ref.refs_holding_resource_alive -= resource.count;
+ // Keep the newest return sync token that has data.
+ // TODO(jbauman): Handle the case with two valid sync tokens.
+ if (resource.sync_token.HasData())
+ ref.sync_token = resource.sync_token;
+ if (ref.refs_holding_resource_alive == 0) {
+ cc::ReturnedResource returned = resource;
+ returned.sync_token = ref.sync_token;
+ returned.count = ref.refs_received_from_child;
+ resources_available_to_return.push_back(returned);
+ resource_id_info_map_.erase(count_it);
+ }
+ }
+
+ client_->ReturnResources(resources_available_to_return);
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/frame_sinks/surface_resource_holder.h b/chromium/components/viz/service/frame_sinks/surface_resource_holder.h
new file mode 100644
index 00000000000..33aed9a322e
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/surface_resource_holder.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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_FRAME_SINKS_SURFACE_RESOURCE_HOLDER_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_SURFACE_RESOURCE_HOLDER_H_
+
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "cc/base/resource_id.h"
+#include "cc/resources/returned_resource.h"
+#include "cc/resources/transferable_resource.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+class SurfaceResourceHolderClient;
+
+// A SurfaceResourceHolder manages the lifetime of resources submitted by a
+// particular SurfaceFactory. Each resource is held by the service until
+// it is no longer referenced by any pending frames or by any
+// resource providers.
+class VIZ_SERVICE_EXPORT SurfaceResourceHolder {
+ public:
+ explicit SurfaceResourceHolder(SurfaceResourceHolderClient* client);
+ ~SurfaceResourceHolder();
+
+ void Reset();
+ void ReceiveFromChild(const std::vector<cc::TransferableResource>& resources);
+ void RefResources(const std::vector<cc::TransferableResource>& resources);
+ void UnrefResources(const std::vector<cc::ReturnedResource>& resources);
+
+ private:
+ SurfaceResourceHolderClient* client_;
+
+ struct ResourceRefs {
+ ResourceRefs();
+
+ int refs_received_from_child;
+ int refs_holding_resource_alive;
+ gpu::SyncToken sync_token;
+ };
+ // Keeps track of the number of users currently in flight for each resource
+ // ID we've received from the client. When this counter hits zero for a
+ // particular resource, that ID is available to return to the client with
+ // the most recently given non-empty sync token.
+ using ResourceIdInfoMap = std::unordered_map<cc::ResourceId, ResourceRefs>;
+ ResourceIdInfoMap resource_id_info_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfaceResourceHolder);
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_SURFACE_RESOURCE_HOLDER_H_
diff --git a/chromium/components/viz/service/frame_sinks/surface_resource_holder_client.h b/chromium/components/viz/service/frame_sinks/surface_resource_holder_client.h
new file mode 100644
index 00000000000..31b56e17d93
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/surface_resource_holder_client.h
@@ -0,0 +1,25 @@
+// 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_FRAME_SINKS_SURFACE_RESOURCE_HOLDER_CLIENT_H_
+#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_SURFACE_RESOURCE_HOLDER_CLIENT_H_
+
+#include "cc/resources/returned_resource.h"
+#include "components/viz/service/viz_service_export.h"
+
+namespace viz {
+
+class VIZ_SERVICE_EXPORT SurfaceResourceHolderClient {
+ public:
+ virtual ~SurfaceResourceHolderClient() = default;
+
+ // ReturnResources gets called when the display compositor is done using the
+ // resources so that the client can use them.
+ virtual void ReturnResources(
+ const std::vector<cc::ReturnedResource>& resources) = 0;
+};
+
+} // namespace viz
+
+#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_SURFACE_RESOURCE_HOLDER_CLIENT_H_
diff --git a/chromium/components/viz/service/frame_sinks/surface_synchronization_unittest.cc b/chromium/components/viz/service/frame_sinks/surface_synchronization_unittest.cc
new file mode 100644
index 00000000000..245b52fcbbb
--- /dev/null
+++ b/chromium/components/viz/service/frame_sinks/surface_synchronization_unittest.cc
@@ -0,0 +1,1605 @@
+// 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 "base/containers/flat_set.h"
+#include "cc/test/begin_frame_args_test.h"
+#include "cc/test/compositor_frame_helpers.h"
+#include "cc/test/fake_external_begin_frame_source.h"
+#include "cc/test/fake_surface_observer.h"
+#include "cc/test/mock_compositor_frame_sink_support_client.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using cc::test::MockCompositorFrameSinkSupportClient;
+using cc::test::MakeCompositorFrame;
+using testing::_;
+using testing::Eq;
+using testing::IsEmpty;
+using testing::UnorderedElementsAre;
+
+namespace viz {
+namespace test {
+namespace {
+
+constexpr bool kIsRoot = true;
+constexpr bool kIsChildRoot = false;
+constexpr bool kHandlesFrameSinkIdInvalidation = true;
+constexpr bool kNeedsSyncPoints = true;
+constexpr FrameSinkId kDisplayFrameSink(2, 0);
+constexpr FrameSinkId kParentFrameSink(3, 0);
+constexpr FrameSinkId kChildFrameSink1(65563, 0);
+constexpr FrameSinkId kChildFrameSink2(65564, 0);
+constexpr FrameSinkId kArbitraryFrameSink(1337, 7331);
+
+std::vector<SurfaceId> empty_surface_ids() {
+ return std::vector<SurfaceId>();
+}
+
+SurfaceId MakeSurfaceId(const FrameSinkId& frame_sink_id, uint32_t local_id) {
+ return SurfaceId(
+ frame_sink_id,
+ LocalSurfaceId(local_id, base::UnguessableToken::Deserialize(0, 1u)));
+}
+
+} // namespace
+
+class FakeExternalBeginFrameSourceClient
+ : public cc::FakeExternalBeginFrameSource::Client {
+ public:
+ FakeExternalBeginFrameSourceClient() = default;
+ ~FakeExternalBeginFrameSourceClient() = default;
+
+ bool has_observers() const { return observer_count_ > 0; }
+
+ // cc::FakeExternalBeginFrameSource::Client implementation:
+ void OnAddObserver(cc::BeginFrameObserver* obs) override {
+ ++observer_count_;
+ }
+
+ void OnRemoveObserver(cc::BeginFrameObserver* obs) override {
+ DCHECK_GT(observer_count_, 0);
+ --observer_count_;
+ }
+
+ private:
+ int observer_count_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeExternalBeginFrameSourceClient);
+};
+
+class SurfaceSynchronizationTest : public testing::Test {
+ public:
+ SurfaceSynchronizationTest()
+ : frame_sink_manager_(nullptr /* display_provider */,
+ cc::SurfaceManager::LifetimeType::REFERENCES),
+ surface_observer_(false) {}
+ ~SurfaceSynchronizationTest() override {}
+
+ CompositorFrameSinkSupport& display_support() { return *supports_[0]; }
+ cc::Surface* display_surface() {
+ return display_support().GetCurrentSurfaceForTesting();
+ }
+
+ CompositorFrameSinkSupport& parent_support() { return *supports_[1]; }
+ cc::Surface* parent_surface() {
+ return parent_support().GetCurrentSurfaceForTesting();
+ }
+
+ CompositorFrameSinkSupport& child_support1() { return *supports_[2]; }
+ cc::Surface* child_surface1() {
+ return child_support1().GetCurrentSurfaceForTesting();
+ }
+
+ CompositorFrameSinkSupport& child_support2() { return *supports_[3]; }
+ cc::Surface* child_surface2() {
+ return child_support2().GetCurrentSurfaceForTesting();
+ }
+
+ CompositorFrameSinkSupport& support(int index) { return *supports_[index]; }
+ cc::Surface* surface(int index) {
+ return support(index).GetCurrentSurfaceForTesting();
+ }
+
+ FrameSinkManagerImpl& frame_sink_manager() { return frame_sink_manager_; }
+
+ // Returns all the references where |surface_id| is the parent.
+ const base::flat_set<SurfaceId>& GetChildReferences(
+ const SurfaceId& surface_id) {
+ return frame_sink_manager()
+ .surface_manager()
+ ->GetSurfacesReferencedByParent(surface_id);
+ }
+
+ // Returns true if there is a temporary reference for |surface_id|.
+ bool HasTemporaryReference(const SurfaceId& surface_id) {
+ return frame_sink_manager().surface_manager()->HasTemporaryReference(
+ surface_id);
+ }
+
+ cc::FakeExternalBeginFrameSource* begin_frame_source() {
+ return begin_frame_source_.get();
+ }
+
+ void SendNextBeginFrame() {
+ // Creep the time forward so that any cc::BeginFrameArgs is not equal to the
+ // last one otherwise we violate the BeginFrameSource contract.
+ now_src_->Advance(cc::BeginFrameArgs::DefaultInterval());
+ cc::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
+ BEGINFRAME_FROM_HERE, now_src_.get());
+ begin_frame_source_->TestOnBeginFrame(args);
+ }
+
+ cc::FakeSurfaceObserver& surface_observer() { return surface_observer_; }
+
+ // testing::Test:
+ void SetUp() override {
+ testing::Test::SetUp();
+
+ begin_frame_source_ =
+ base::MakeUnique<cc::FakeExternalBeginFrameSource>(0.f, false);
+ begin_frame_source_->SetClient(&begin_frame_source_client_);
+ now_src_ = base::MakeUnique<base::SimpleTestTickClock>();
+ frame_sink_manager_.surface_manager()->AddObserver(&surface_observer_);
+ supports_.push_back(CompositorFrameSinkSupport::Create(
+ &support_client_, &frame_sink_manager_, kDisplayFrameSink, kIsRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints));
+ supports_.push_back(CompositorFrameSinkSupport::Create(
+ &support_client_, &frame_sink_manager_, kParentFrameSink, kIsChildRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints));
+ supports_.push_back(CompositorFrameSinkSupport::Create(
+ &support_client_, &frame_sink_manager_, kChildFrameSink1, kIsChildRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints));
+ supports_.push_back(CompositorFrameSinkSupport::Create(
+ &support_client_, &frame_sink_manager_, kChildFrameSink2, kIsChildRoot,
+ kHandlesFrameSinkIdInvalidation, kNeedsSyncPoints));
+
+ // Normally, the BeginFrameSource would be registered by the Display. We
+ // register it here so that BeginFrames are received by the display support,
+ // for use in the PassesOnBeginFrameAcks test. Other supports do not receive
+ // BeginFrames, since the frame sink hierarchy is not set up in this test.
+ frame_sink_manager_.RegisterBeginFrameSource(begin_frame_source_.get(),
+ kDisplayFrameSink);
+ }
+
+ void TearDown() override {
+ frame_sink_manager_.surface_manager()->RemoveObserver(&surface_observer_);
+ frame_sink_manager_.UnregisterBeginFrameSource(begin_frame_source_.get());
+
+ begin_frame_source_->SetClient(nullptr);
+ begin_frame_source_.reset();
+
+ supports_.clear();
+
+ surface_observer_.Reset();
+ }
+
+ bool IsMarkedForDestruction(const SurfaceId& surface_id) {
+ return frame_sink_manager_.surface_manager()->IsMarkedForDestruction(
+ surface_id);
+ }
+
+ cc::Surface* GetSurfaceForId(const SurfaceId& surface_id) {
+ return frame_sink_manager_.surface_manager()->GetSurfaceForId(surface_id);
+ }
+
+ protected:
+ testing::NiceMock<MockCompositorFrameSinkSupportClient> support_client_;
+
+ private:
+ FrameSinkManagerImpl frame_sink_manager_;
+ cc::FakeSurfaceObserver surface_observer_;
+ FakeExternalBeginFrameSourceClient begin_frame_source_client_;
+ std::unique_ptr<cc::FakeExternalBeginFrameSource> begin_frame_source_;
+ std::unique_ptr<base::SimpleTestTickClock> now_src_;
+ std::vector<std::unique_ptr<CompositorFrameSinkSupport>> supports_;
+
+ DISALLOW_COPY_AND_ASSIGN(SurfaceSynchronizationTest);
+};
+
+// The display root surface should have a surface reference from the top-level
+// root added/removed when a cc::CompositorFrame is submitted with a new
+// SurfaceId.
+TEST_F(SurfaceSynchronizationTest, RootSurfaceReceivesReferences) {
+ const SurfaceId display_id_first = MakeSurfaceId(kDisplayFrameSink, 1);
+ const SurfaceId display_id_second = MakeSurfaceId(kDisplayFrameSink, 2);
+
+ // Submit a cc::CompositorFrame for the first display root surface.
+ display_support().SubmitCompositorFrame(display_id_first.local_surface_id(),
+ MakeCompositorFrame());
+
+ // A surface reference from the top-level root is added and there shouldn't be
+ // a temporary reference.
+ EXPECT_FALSE(HasTemporaryReference(display_id_first));
+ EXPECT_THAT(GetChildReferences(
+ frame_sink_manager().surface_manager()->GetRootSurfaceId()),
+ UnorderedElementsAre(display_id_first));
+
+ // Submit a cc::CompositorFrame for the second display root surface.
+ display_support().SubmitCompositorFrame(display_id_second.local_surface_id(),
+ MakeCompositorFrame());
+
+ // A surface reference from the top-level root to |display_id_second| should
+ // be added and the reference to |display_root_first| removed.
+ EXPECT_FALSE(HasTemporaryReference(display_id_second));
+ EXPECT_THAT(GetChildReferences(
+ frame_sink_manager().surface_manager()->GetRootSurfaceId()),
+ UnorderedElementsAre(display_id_second));
+
+ // cc::Surface |display_id_first| is unreachable and should get deleted.
+ EXPECT_EQ(nullptr, GetSurfaceForId(display_id_first));
+}
+
+// The parent cc::Surface is blocked on |child_id1| and |child_id2|.
+TEST_F(SurfaceSynchronizationTest, BlockedOnTwo) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id1, child_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // parent_support is blocked on |child_id1| and |child_id2|.
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id1, child_id2));
+
+ // Submit a cc::CompositorFrame without any dependencies to |child_id1|.
+ // parent_support should now only be blocked on |child_id2|.
+ child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
+ MakeCompositorFrame());
+
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+
+ // Submit a cc::CompositorFrame without any dependencies to |child_id2|.
+ // parent_support should be activated.
+ child_support2().SubmitCompositorFrame(child_id2.local_surface_id(),
+ MakeCompositorFrame());
+
+ EXPECT_FALSE(child_surface2()->has_deadline());
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+}
+
+// The parent cc::Surface is blocked on |child_id2| which is blocked on
+// |child_id3|.
+TEST_F(SurfaceSynchronizationTest, BlockedChain) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id1}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // parent_support is blocked on |child_id1|.
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id1));
+ // The parent should not report damage until it activates.
+ EXPECT_FALSE(surface_observer().IsSurfaceDamaged(parent_id));
+
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame({child_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // child_support1 should now be blocked on |child_id2|.
+ EXPECT_TRUE(child_surface1()->has_deadline());
+ EXPECT_FALSE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+ // The parent and child should not report damage until they activate.
+ EXPECT_FALSE(surface_observer().IsSurfaceDamaged(parent_id));
+ EXPECT_FALSE(surface_observer().IsSurfaceDamaged(child_id1));
+
+ // The parent should still be blocked on |child_id1| because it's pending.
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id1));
+
+ // Submit a cc::CompositorFrame without any dependencies to |child_id2|.
+ // parent_support should be activated.
+ child_support2().SubmitCompositorFrame(
+ child_id2.local_surface_id(),
+ MakeCompositorFrame(empty_surface_ids(), empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ EXPECT_FALSE(child_surface2()->has_deadline());
+
+ // child_surface1 should now be active.
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(), IsEmpty());
+
+ // parent_surface should now be active.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+
+ // All three surfaces |parent_id|, |child_id1|, and |child_id2| should
+ // now report damage. This would trigger a new display frame.
+ EXPECT_TRUE(surface_observer().IsSurfaceDamaged(parent_id));
+ EXPECT_TRUE(surface_observer().IsSurfaceDamaged(child_id1));
+ EXPECT_TRUE(surface_observer().IsSurfaceDamaged(child_id2));
+}
+
+// parent_surface and child_surface1 are blocked on |child_id2|.
+TEST_F(SurfaceSynchronizationTest, TwoBlockedOnOne) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // parent_support is blocked on |child_id2|.
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+
+ // child_support1 should now be blocked on |child_id2|.
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame({child_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ EXPECT_TRUE(child_surface1()->has_deadline());
+ EXPECT_FALSE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+
+ // The parent should still be blocked on |child_id2|.
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+
+ // Submit a cc::CompositorFrame without any dependencies to |child_id2|.
+ // parent_support should be activated.
+ child_support2().SubmitCompositorFrame(child_id2.local_surface_id(),
+ MakeCompositorFrame());
+
+ EXPECT_FALSE(child_surface2()->has_deadline());
+
+ // child_surface1 should now be active.
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(), IsEmpty());
+
+ // parent_surface should now be active.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+}
+
+// parent_surface is blocked on |child_id1|, and child_surface2 is blocked on
+// |child_id2| until the deadline hits.
+TEST_F(SurfaceSynchronizationTest, DeadlineHits) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id1}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // parent_support is blocked on |child_id1|.
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id1));
+
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame({child_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // child_support1 should now be blocked on |child_id2|.
+ EXPECT_TRUE(child_surface1()->has_deadline());
+ EXPECT_FALSE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+
+ // The parent should still be blocked on |child_id1| because it's pending.
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id1));
+
+ for (int i = 0; i < 3; ++i) {
+ SendNextBeginFrame();
+ // There is still a looming deadline! Eeek!
+ EXPECT_TRUE(parent_surface()->has_deadline());
+
+ // parent_support is still blocked on |child_id1|.
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id1));
+
+ // child_support1 is still blocked on |child_id2|.
+ EXPECT_FALSE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+ }
+
+ SendNextBeginFrame();
+
+ // The deadline has passed.
+ EXPECT_FALSE(parent_surface()->has_deadline());
+
+ // parent_surface has been activated.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+
+ // child_surface1 has been activated.
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(), IsEmpty());
+}
+
+// This test verifies at the cc::Surface activates once a cc::CompositorFrame is
+// submitted that has no unresolved dependencies.
+TEST_F(SurfaceSynchronizationTest, NewFrameOverridesOldDependencies) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId arbitrary_id = MakeSurfaceId(kArbitraryFrameSink, 1);
+
+ // Submit a cc::CompositorFrame that depends on |arbitrary_id|.
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({arbitrary_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // Verify that the cc::CompositorFrame is blocked on |arbitrary_id|.
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(arbitrary_id));
+
+ // Submit a cc::CompositorFrame that has no dependencies.
+ parent_support().SubmitCompositorFrame(parent_id.local_surface_id(),
+ MakeCompositorFrame());
+
+ // Verify that the cc::CompositorFrame has been activated.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+}
+
+// This test verifies that a pending cc::CompositorFrame does not affect surface
+// references. A new surface from a child will continue to exist as a temporary
+// reference until the parent's frame activates.
+TEST_F(SurfaceSynchronizationTest, OnlyActiveFramesAffectSurfaceReferences) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+
+ // child_support1 submits a cc::CompositorFrame without any dependencies.
+ // DidReceiveCompositorFrameAck should call on immediate activation.
+ EXPECT_CALL(support_client_, DidReceiveCompositorFrameAck(_)).Times(1);
+ child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
+ MakeCompositorFrame());
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+
+ // Verify that the child surface is not blocked.
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(), IsEmpty());
+
+ // Verify that there's a temporary reference for |child_id1|.
+ EXPECT_TRUE(HasTemporaryReference(child_id1));
+
+ // parent_support submits a cc::CompositorFrame that depends on |child_id1|
+ // (which is already active) and |child_id2|. Thus, the parent should not
+ // activate immediately. DidReceiveCompositorFrameAck should not be called
+ // immediately because the parent cc::CompositorFrame is also blocked on
+ // |child_id2|.
+ EXPECT_CALL(support_client_, DidReceiveCompositorFrameAck(_)).Times(0);
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id2}, {child_id1},
+ std::vector<cc::TransferableResource>()));
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+ EXPECT_THAT(GetChildReferences(parent_id), IsEmpty());
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+
+ // Verify that there's a temporary reference for |child_id1| that still
+ // exists.
+ EXPECT_TRUE(HasTemporaryReference(child_id1));
+
+ // child_support2 submits a cc::CompositorFrame without any dependencies.
+ // Both the child and the parent should immediately ACK CompositorFrames
+ // on activation.
+ EXPECT_CALL(support_client_, DidReceiveCompositorFrameAck(_)).Times(2);
+ child_support2().SubmitCompositorFrame(child_id2.local_surface_id(),
+ MakeCompositorFrame());
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+
+ // Verify that the child surface is not blocked.
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(), IsEmpty());
+
+ // Verify that the parent surface's cc::CompositorFrame has activated and that
+ // the temporary reference has been replaced by a permanent one.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+ EXPECT_FALSE(HasTemporaryReference(child_id1));
+ EXPECT_THAT(GetChildReferences(parent_id), UnorderedElementsAre(child_id1));
+}
+
+// This test verifies that we do not double count returned resources when a
+// cc::CompositorFrame starts out as pending, then becomes active, and then is
+// replaced with another active cc::CompositorFrame.
+TEST_F(SurfaceSynchronizationTest, ResourcesOnlyReturnedOnce) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 1);
+
+ // The parent submits a cc::CompositorFrame that depends on |child_id| before
+ // the child submits a cc::CompositorFrame. The cc::CompositorFrame also has
+ // resources in its resource list.
+ cc::TransferableResource resource;
+ resource.id = 1337;
+ resource.format = ALPHA_8;
+ resource.filter = 1234;
+ resource.size = gfx::Size(1234, 5678);
+ std::vector<cc::TransferableResource> resource_list = {resource};
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id}, empty_surface_ids(), resource_list));
+
+ // Verify that the cc::CompositorFrame is blocked on |child_id|.
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id));
+
+ child_support1().SubmitCompositorFrame(
+ child_id.local_surface_id(),
+ MakeCompositorFrame(empty_surface_ids(), empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // Verify that the child cc::CompositorFrame activates immediately.
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(), IsEmpty());
+
+ // Verify that the parent has activated.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+
+ std::vector<cc::ReturnedResource> returned_resources = {
+ resource.ToReturnedResource()};
+ EXPECT_CALL(support_client_,
+ DidReceiveCompositorFrameAck(returned_resources));
+
+ // The parent submits a cc::CompositorFrame without any dependencies. That
+ // frame should activate immediately, replacing the earlier frame. The
+ // resource from the earlier frame should be returned to the client.
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({empty_surface_ids()}, {empty_surface_ids()},
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+}
+
+// The parent cc::Surface is blocked on |child_id2| which is blocked on
+// |child_id3|. child_support1 evicts its blocked cc::Surface. The parent
+// surface should activate.
+TEST_F(SurfaceSynchronizationTest, EvictSurfaceWithPendingFrame) {
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+
+ // Submit a cc::CompositorFrame that depends on |child_id1|.
+ parent_support().SubmitCompositorFrame(
+ parent_id1.local_surface_id(),
+ MakeCompositorFrame({child_id1}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // Verify that the cc::CompositorFrame is blocked on |child_id1|.
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id1));
+
+ // Submit a cc::CompositorFrame that depends on |child_id2|.
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame({child_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // Verify that the cc::CompositorFrame is blocked on |child_id2|.
+ EXPECT_FALSE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+
+ // Evict child_support1's current cc::Surface.
+ // TODO(fsamuel): EvictCurrentSurface => EvictCurrentSurface.
+ child_support1().EvictCurrentSurface();
+
+ // The parent cc::Surface should immediately activate.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+ EXPECT_FALSE(parent_surface()->has_deadline());
+}
+
+// This test verifies that if a surface has both a pending and active
+// cc::CompositorFrame and the pending cc::CompositorFrame activates, replacing
+// the existing active cc::CompositorFrame, then the surface reference hierarchy
+// will be updated allowing garbage collection of surfaces that are no longer
+// referenced.
+TEST_F(SurfaceSynchronizationTest, DropStaleReferencesAfterActivation) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+
+ // The parent submits a cc::CompositorFrame that depends on |child_id1| before
+ // the child submits a cc::CompositorFrame.
+ EXPECT_CALL(support_client_, DidReceiveCompositorFrameAck(_)).Times(0);
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id1}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // Verify that the cc::CompositorFrame is blocked on |child_id|.
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id1));
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+
+ // Verify that no references are added while the cc::CompositorFrame is
+ // pending.
+ EXPECT_THAT(GetChildReferences(parent_id), IsEmpty());
+
+ // DidReceiveCompositorFrameAck should get called twice: once for the child
+ // and once for the now active parent cc::CompositorFrame.
+ EXPECT_CALL(support_client_, DidReceiveCompositorFrameAck(_)).Times(2);
+ child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
+ MakeCompositorFrame());
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+
+ // Verify that the child cc::CompositorFrame activates immediately.
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_THAT(child_surface1()->activation_dependencies(), IsEmpty());
+
+ // Verify that the parent cc::Surface has activated.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+
+ // Submit a new parent cc::CompositorFrame to add a reference.
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame(empty_surface_ids(), {child_id1},
+ std::vector<cc::TransferableResource>()));
+
+ // Verify that the parent cc::Surface has activated.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+
+ // Verify that there is no temporary reference for the child and that
+ // the reference from the parent to the child still exists.
+ EXPECT_FALSE(HasTemporaryReference(child_id1));
+ EXPECT_THAT(GetChildReferences(parent_id), UnorderedElementsAre(child_id1));
+
+ // The parent submits another cc::CompositorFrame that depends on |child_id2|.
+ // Submitting a pending cc::CompositorFrame will not trigger a
+ // CompositorFrameAck.
+ EXPECT_CALL(support_client_, DidReceiveCompositorFrameAck(_)).Times(0);
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+
+ // The parent surface should now have both a pending and activate
+ // cc::CompositorFrame. Verify that the set of child references from
+ // |parent_id| are only from the active cc::CompositorFrame.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(),
+ UnorderedElementsAre(child_id2));
+ EXPECT_THAT(GetChildReferences(parent_id), UnorderedElementsAre(child_id1));
+
+ child_support2().SubmitCompositorFrame(child_id2.local_surface_id(),
+ MakeCompositorFrame());
+
+ // Verify that the parent cc::Surface has activated and no longer has a
+ // pending cc::CompositorFrame. Also verify that |child_id1| is no longer a
+ // child reference of |parent_id|.
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
+ // The parent will not immediately refer to the child until it submits a new
+ // cc::CompositorFrame with the reference.
+ EXPECT_THAT(GetChildReferences(parent_id), IsEmpty());
+}
+
+// Checks whether the latency info are moved to the new surface from the old
+// one when LocalSurfaceId changes. No frame has unresolved dependencies.
+TEST_F(SurfaceSynchronizationTest,
+ LatencyInfoCarriedOverOnResize_NoUnresolvedDependencies) {
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId parent_id2 = MakeSurfaceId(kParentFrameSink, 2);
+ const ui::LatencyComponentType latency_type1 =
+ ui::BROWSER_SNAPSHOT_FRAME_NUMBER_COMPONENT;
+ const int64_t latency_id1 = 234;
+ const int64_t latency_sequence_number1 = 5645432;
+ const ui::LatencyComponentType latency_type2 = ui::TAB_SHOW_COMPONENT;
+ const int64_t latency_id2 = 31434351;
+ const int64_t latency_sequence_number2 = 663788;
+
+ // Submit a frame with latency info
+ ui::LatencyInfo info;
+ info.AddLatencyNumber(latency_type1, latency_id1, latency_sequence_number1);
+
+ cc::CompositorFrame frame = MakeCompositorFrame();
+ frame.metadata.latency_info.push_back(info);
+
+ parent_support().SubmitCompositorFrame(parent_id1.local_surface_id(),
+ std::move(frame));
+
+ // Verify that the old surface has an active frame and no pending frame.
+ cc::Surface* old_surface = GetSurfaceForId(parent_id1);
+ ASSERT_NE(nullptr, old_surface);
+ EXPECT_TRUE(old_surface->HasActiveFrame());
+ EXPECT_FALSE(old_surface->HasPendingFrame());
+
+ // Submit another frame with some other latency info and a different
+ // LocalSurfaceId.
+ ui::LatencyInfo info2;
+ info2.AddLatencyNumber(latency_type2, latency_id2, latency_sequence_number2);
+
+ cc::CompositorFrame frame2 = MakeCompositorFrame();
+ frame2.metadata.latency_info.push_back(info2);
+
+ parent_support().SubmitCompositorFrame(parent_id2.local_surface_id(),
+ std::move(frame2));
+
+ // Verify that the new surface has an active frame and no pending frames.
+ cc::Surface* surface = GetSurfaceForId(parent_id2);
+ ASSERT_NE(nullptr, surface);
+ EXPECT_TRUE(surface->HasActiveFrame());
+ EXPECT_FALSE(surface->HasPendingFrame());
+
+ // Verify that the new surface has both latency info elements.
+ std::vector<ui::LatencyInfo> info_list;
+ surface->TakeLatencyInfo(&info_list);
+ EXPECT_EQ(2u, info_list.size());
+
+ ui::LatencyInfo aggregated_latency_info = info_list[0];
+ aggregated_latency_info.AddNewLatencyFrom(info_list[1]);
+
+ // Two components are the original ones, and the third one is
+ // DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT, logged on compositor frame
+ // submit.
+ EXPECT_EQ(3u, aggregated_latency_info.latency_components().size());
+
+ ui::LatencyInfo::LatencyComponent comp1;
+ EXPECT_TRUE(
+ aggregated_latency_info.FindLatency(latency_type1, latency_id1, &comp1));
+ EXPECT_EQ(latency_sequence_number1, comp1.sequence_number);
+ EXPECT_TRUE(
+ aggregated_latency_info.FindLatency(latency_type2, latency_id2, nullptr));
+ EXPECT_TRUE(aggregated_latency_info.FindLatency(
+ ui::DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT, nullptr));
+}
+
+// Checks whether the latency info are moved to the new surface from the old
+// one when LocalSurfaceId changes. Old surface has unresolved
+// dependencies.
+TEST_F(SurfaceSynchronizationTest,
+ LatencyInfoCarriedOverOnResize_OldSurfaceHasPendingAndActiveFrame) {
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId parent_id2 = MakeSurfaceId(kParentFrameSink, 2);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 1);
+
+ const ui::LatencyComponentType latency_type1 =
+ ui::BROWSER_SNAPSHOT_FRAME_NUMBER_COMPONENT;
+ const int64_t latency_id1 = 234;
+ const int64_t latency_sequence_number1 = 5645432;
+ const ui::LatencyComponentType latency_type2 = ui::TAB_SHOW_COMPONENT;
+ const int64_t latency_id2 = 31434351;
+ const int64_t latency_sequence_number2 = 663788;
+
+ // Submit a frame with no unresolved dependecy.
+ ui::LatencyInfo info;
+ info.AddLatencyNumber(latency_type1, latency_id1, latency_sequence_number1);
+
+ cc::CompositorFrame frame = MakeCompositorFrame();
+ frame.metadata.latency_info.push_back(info);
+
+ parent_support().SubmitCompositorFrame(parent_id1.local_surface_id(),
+ std::move(frame));
+
+ // Submit a frame with unresolved dependencies.
+ ui::LatencyInfo info2;
+ info2.AddLatencyNumber(latency_type2, latency_id2, latency_sequence_number2);
+
+ cc::CompositorFrame frame2 = MakeCompositorFrame(
+ {child_id}, empty_surface_ids(), std::vector<cc::TransferableResource>());
+ frame2.metadata.latency_info.push_back(info2);
+
+ parent_support().SubmitCompositorFrame(parent_id1.local_surface_id(),
+ std::move(frame2));
+
+ // Verify that the old surface has both an active and a pending frame.
+ cc::Surface* old_surface = GetSurfaceForId(parent_id1);
+ ASSERT_NE(nullptr, old_surface);
+ EXPECT_TRUE(old_surface->HasActiveFrame());
+ EXPECT_TRUE(old_surface->HasPendingFrame());
+
+ // Submit a frame with a new local surface id.
+ parent_support().SubmitCompositorFrame(parent_id2.local_surface_id(),
+ MakeCompositorFrame());
+
+ // Verify that the new surface has an active frame only.
+ cc::Surface* surface = GetSurfaceForId(parent_id2);
+ ASSERT_NE(nullptr, surface);
+ EXPECT_TRUE(surface->HasActiveFrame());
+ EXPECT_FALSE(surface->HasPendingFrame());
+
+ // Verify that the new surface has latency info from both active and pending
+ // frame of the old surface.
+ std::vector<ui::LatencyInfo> info_list;
+ surface->TakeLatencyInfo(&info_list);
+ EXPECT_EQ(2u, info_list.size());
+
+ ui::LatencyInfo aggregated_latency_info = info_list[0];
+ aggregated_latency_info.AddNewLatencyFrom(info_list[1]);
+
+ // Two components are the original ones, and the third one is
+ // DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT, logged on compositor frame
+ // submit.
+ EXPECT_EQ(3u, aggregated_latency_info.latency_components().size());
+
+ ui::LatencyInfo::LatencyComponent comp1;
+ EXPECT_TRUE(
+ aggregated_latency_info.FindLatency(latency_type1, latency_id1, &comp1));
+ EXPECT_EQ(latency_sequence_number1, comp1.sequence_number);
+ EXPECT_TRUE(
+ aggregated_latency_info.FindLatency(latency_type2, latency_id2, nullptr));
+ EXPECT_TRUE(aggregated_latency_info.FindLatency(
+ ui::DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT, nullptr));
+}
+
+// Checks whether the latency info are moved to the new surface from the old
+// one when LocalSurfaceId changes. The new surface has unresolved
+// dependencies.
+TEST_F(SurfaceSynchronizationTest,
+ LatencyInfoCarriedOverOnResize_NewSurfaceHasPendingFrame) {
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId parent_id2 = MakeSurfaceId(kParentFrameSink, 2);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 1);
+
+ const ui::LatencyComponentType latency_type1 =
+ ui::BROWSER_SNAPSHOT_FRAME_NUMBER_COMPONENT;
+ const int64_t latency_id1 = 234;
+ const int64_t latency_sequence_number1 = 5645432;
+ const ui::LatencyComponentType latency_type2 = ui::TAB_SHOW_COMPONENT;
+ const int64_t latency_id2 = 31434351;
+ const int64_t latency_sequence_number2 = 663788;
+
+ // Submit a frame with no unresolved dependencies.
+ ui::LatencyInfo info;
+ info.AddLatencyNumber(latency_type1, latency_id1, latency_sequence_number1);
+
+ cc::CompositorFrame frame = MakeCompositorFrame();
+ frame.metadata.latency_info.push_back(info);
+
+ parent_support().SubmitCompositorFrame(parent_id1.local_surface_id(),
+ std::move(frame));
+
+ // Verify that the old surface has an active frame only.
+ cc::Surface* old_surface = GetSurfaceForId(parent_id1);
+ ASSERT_NE(nullptr, old_surface);
+ EXPECT_TRUE(old_surface->HasActiveFrame());
+ EXPECT_FALSE(old_surface->HasPendingFrame());
+
+ // Submit a frame with a new local surface id and with unresolved
+ // dependencies.
+ ui::LatencyInfo info2;
+ info2.AddLatencyNumber(latency_type2, latency_id2, latency_sequence_number2);
+
+ cc::CompositorFrame frame2 = MakeCompositorFrame(
+ {child_id}, empty_surface_ids(), std::vector<cc::TransferableResource>());
+ frame2.metadata.latency_info.push_back(info2);
+
+ parent_support().SubmitCompositorFrame(parent_id2.local_surface_id(),
+ std::move(frame2));
+
+ // Verify that the new surface has a pending frame and no active frame.
+ cc::Surface* surface = GetSurfaceForId(parent_id2);
+ ASSERT_NE(nullptr, surface);
+ EXPECT_TRUE(surface->HasPendingFrame());
+ EXPECT_FALSE(surface->HasActiveFrame());
+
+ // Resolve the dependencies. The frame in parent's surface must become active.
+ child_support1().SubmitCompositorFrame(child_id.local_surface_id(),
+ MakeCompositorFrame());
+ EXPECT_FALSE(surface->HasPendingFrame());
+ EXPECT_TRUE(surface->HasActiveFrame());
+
+ // Both latency info elements must exist in the now-activated frame of the
+ // new surface.
+ std::vector<ui::LatencyInfo> info_list;
+ surface->TakeLatencyInfo(&info_list);
+ EXPECT_EQ(2u, info_list.size());
+
+ ui::LatencyInfo aggregated_latency_info = info_list[0];
+ aggregated_latency_info.AddNewLatencyFrom(info_list[1]);
+
+ // Two components are the original ones, and the third one is
+ // DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT, logged on compositor frame
+ // submit.
+ EXPECT_EQ(3u, aggregated_latency_info.latency_components().size());
+
+ ui::LatencyInfo::LatencyComponent comp1;
+ EXPECT_TRUE(
+ aggregated_latency_info.FindLatency(latency_type1, latency_id1, &comp1));
+ EXPECT_EQ(latency_sequence_number1, comp1.sequence_number);
+ EXPECT_TRUE(
+ aggregated_latency_info.FindLatency(latency_type2, latency_id2, nullptr));
+ EXPECT_TRUE(aggregated_latency_info.FindLatency(
+ ui::DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT, nullptr));
+}
+
+// Checks that resources and ack are sent together if possible.
+TEST_F(SurfaceSynchronizationTest, ReturnResourcesWithAck) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ cc::TransferableResource resource;
+ resource.id = 1234;
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame(empty_surface_ids(), empty_surface_ids(),
+ {resource}));
+ std::vector<cc::ReturnedResource> returned_resources =
+ cc::TransferableResource::ReturnResources({resource});
+ EXPECT_CALL(support_client_, ReclaimResources(_)).Times(0);
+ EXPECT_CALL(support_client_,
+ DidReceiveCompositorFrameAck(Eq(returned_resources)));
+ parent_support().SubmitCompositorFrame(parent_id.local_surface_id(),
+ MakeCompositorFrame());
+}
+
+// Verifies that if a surface is marked destroyed and a new frame arrives for
+// it, it will be recovered.
+TEST_F(SurfaceSynchronizationTest, SurfaceResurrection) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 3);
+
+ // Create the child surface by submitting a frame to it.
+ EXPECT_EQ(nullptr, GetSurfaceForId(child_id));
+ child_support1().SubmitCompositorFrame(child_id.local_surface_id(),
+ MakeCompositorFrame());
+
+ // Verify that the child surface is created.
+ cc::Surface* surface = GetSurfaceForId(child_id);
+ EXPECT_NE(nullptr, surface);
+
+ // Add a reference from the parent to the child.
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id}, {child_id},
+ std::vector<cc::TransferableResource>()));
+
+ // Attempt to destroy the child surface. The surface must still exist since
+ // the parent needs it but it will be marked as destroyed.
+ child_support1().EvictCurrentSurface();
+ surface = GetSurfaceForId(child_id);
+ EXPECT_NE(nullptr, surface);
+ EXPECT_TRUE(IsMarkedForDestruction(child_id));
+
+ // Child submits another frame to the same local surface id that is marked
+ // destroyed.
+ child_support1().SubmitCompositorFrame(child_id.local_surface_id(),
+ MakeCompositorFrame());
+
+ // Verify that the surface that was marked destroyed is recovered and is being
+ // used again.
+ cc::Surface* surface2 = GetSurfaceForId(child_id);
+ EXPECT_EQ(surface, surface2);
+ EXPECT_FALSE(IsMarkedForDestruction(child_id));
+}
+
+// Verifies that if a LocalSurfaceId belonged to a surface that doesn't
+// exist anymore, it can still be reused for new surfaces.
+TEST_F(SurfaceSynchronizationTest, LocalSurfaceIdIsReusable) {
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 3);
+
+ // Submit the first frame. Creates the surface.
+ child_support1().SubmitCompositorFrame(child_id.local_surface_id(),
+ MakeCompositorFrame());
+ EXPECT_NE(nullptr, GetSurfaceForId(child_id));
+
+ // Add a reference from parent.
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id}, {child_id},
+ std::vector<cc::TransferableResource>()));
+
+ // Remove the reference from parant. This allows us to destroy the surface.
+ parent_support().SubmitCompositorFrame(parent_id.local_surface_id(),
+ MakeCompositorFrame());
+
+ // Destroy the surface.
+ child_support1().EvictCurrentSurface();
+ EXPECT_EQ(nullptr, GetSurfaceForId(child_id));
+
+ // Submit another frame with the same local surface id. This should work fine
+ // and a new surface must be created.
+ child_support1().SubmitCompositorFrame(child_id.local_surface_id(),
+ MakeCompositorFrame());
+ EXPECT_NE(nullptr, GetSurfaceForId(child_id));
+}
+
+// This test verifies that a crash does not occur if garbage collection is
+// triggered during surface dependency resolution. This test triggers garbage
+// collection during surface resolution, by causing an activation to remove
+// a surface subtree from the root. Both the old subtree and the new
+// activated subtree refer to the same dependency. The old subtree was activated
+// by deadline, and the new subtree was activated by a dependency finally
+// resolving.
+TEST_F(SurfaceSynchronizationTest, DependencyTrackingGarbageCollection) {
+ const SurfaceId display_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId parent_id2 = MakeSurfaceId(kParentFrameSink, 2);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 1);
+
+ parent_support().SubmitCompositorFrame(
+ parent_id1.local_surface_id(),
+ MakeCompositorFrame({child_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ display_support().SubmitCompositorFrame(
+ display_id.local_surface_id(),
+ MakeCompositorFrame({parent_id1}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ EXPECT_TRUE(parent_surface()->has_deadline());
+
+ // Advance BeginFrames to trigger a deadline.
+ for (int i = 0; i < 3; ++i) {
+ SendNextBeginFrame();
+ EXPECT_TRUE(display_surface()->has_deadline());
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ }
+ SendNextBeginFrame();
+
+ EXPECT_TRUE(display_surface()->HasActiveFrame());
+ EXPECT_FALSE(display_surface()->HasPendingFrame());
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+
+ parent_support().SubmitCompositorFrame(
+ parent_id2.local_surface_id(),
+ MakeCompositorFrame({child_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ display_support().SubmitCompositorFrame(
+ display_id.local_surface_id(),
+ MakeCompositorFrame({parent_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ // The display surface now has two CompositorFrames. One that is pending,
+ // indirectly blocked on child_id and one that is active, also indirectly
+ // referring to child_id, but activated due to the deadline above.
+ EXPECT_TRUE(display_surface()->HasActiveFrame());
+ EXPECT_TRUE(display_surface()->HasPendingFrame());
+
+ // Submitting a cc::CompositorFrame will trigger garbage collection of the
+ // |parent_id1| subtree. This should not crash.
+ child_support1().SubmitCompositorFrame(child_id.local_surface_id(),
+ MakeCompositorFrame());
+}
+
+// This test verifies that a crash does not occur if garbage collection is
+// triggered when a deadline forces frame activation. This test triggers garbage
+// collection during deadline activation by causing the activation of a display
+// frame to replace a previously activated display frame that was referring to
+// a now-unreachable surface subtree. That subtree gets garbage collected during
+// deadline activation. SurfaceDependencyTracker is also tracking a surface
+// from that subtree due to an unresolved dependency. This test verifies that
+// this dependency resolution does not crash.
+TEST_F(SurfaceSynchronizationTest, GarbageCollectionOnDeadline) {
+ const SurfaceId display_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId parent_id2 = MakeSurfaceId(kParentFrameSink, 2);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 1);
+
+ // |parent_id1| is blocked on |child_id|.
+ parent_support().SubmitCompositorFrame(
+ parent_id1.local_surface_id(),
+ MakeCompositorFrame({child_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ display_support().SubmitCompositorFrame(
+ display_id.local_surface_id(),
+ MakeCompositorFrame({parent_id1}, {parent_id1},
+ std::vector<cc::TransferableResource>()));
+
+ EXPECT_TRUE(display_surface()->has_deadline());
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_TRUE(display_surface()->HasPendingFrame());
+ EXPECT_FALSE(display_surface()->HasActiveFrame());
+
+ // Advance BeginFrames to trigger a deadline. This activates the
+ // cc::CompositorFrame submitted above.
+ for (int i = 0; i < 3; ++i) {
+ SendNextBeginFrame();
+ EXPECT_TRUE(display_surface()->has_deadline());
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ }
+ SendNextBeginFrame();
+ EXPECT_FALSE(display_surface()->has_deadline());
+ EXPECT_FALSE(parent_surface()->has_deadline());
+ EXPECT_FALSE(display_surface()->HasPendingFrame());
+ EXPECT_TRUE(display_surface()->HasActiveFrame());
+
+ // By submitting a display cc::CompositorFrame, and replacing the parent's
+ // cc::CompositorFrame with another surface ID, parent_id1 becomes unreachable
+ // and a candidate for garbage collection.
+ display_support().SubmitCompositorFrame(
+ display_id.local_surface_id(),
+ MakeCompositorFrame({parent_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(display_surface()->has_deadline());
+
+ // Now |parent_id1| is only kept alive by the active |display_id| frame.
+ parent_support().SubmitCompositorFrame(
+ parent_id2.local_surface_id(),
+ MakeCompositorFrame({child_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(display_surface()->has_deadline());
+ EXPECT_TRUE(parent_surface()->has_deadline());
+
+ // SurfaceDependencyTracker should now be tracking |display_id|, |parent_id1|
+ // and |parent_id2|. By activating the pending |display_id| frame by deadline,
+ // |parent_id1| becomes unreachable and is garbage collected while
+ // SurfaceDependencyTracker is in the process of activating surfaces. This
+ // should not cause a crash or use-after-free.
+ for (int i = 0; i < 3; ++i) {
+ SendNextBeginFrame();
+ EXPECT_TRUE(display_surface()->has_deadline());
+ }
+ SendNextBeginFrame();
+ EXPECT_FALSE(display_surface()->has_deadline());
+}
+
+// This test verifies that a cc::CompositorFrame will only blocked on embedded
+// surfaces but not on other retained surface IDs in the cc::CompositorFrame.
+TEST_F(SurfaceSynchronizationTest, OnlyBlockOnEmbeddedSurfaces) {
+ const SurfaceId display_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId parent_id2 = MakeSurfaceId(kParentFrameSink, 2);
+
+ // Submitting a cc::CompositorFrame with |parent_id2| so that the display
+ // cc::CompositorFrame can hold a reference to it.
+ parent_support().SubmitCompositorFrame(parent_id2.local_surface_id(),
+ MakeCompositorFrame());
+
+ display_support().SubmitCompositorFrame(
+ display_id.local_surface_id(),
+ MakeCompositorFrame({parent_id1}, {parent_id2},
+ std::vector<cc::TransferableResource>()));
+
+ EXPECT_TRUE(display_surface()->HasPendingFrame());
+ EXPECT_FALSE(display_surface()->HasActiveFrame());
+ EXPECT_TRUE(display_surface()->has_deadline());
+
+ // Verify that the display cc::CompositorFrame will only block on |parent_id1|
+ // but not |parent_id2|.
+ EXPECT_THAT(display_surface()->activation_dependencies(),
+ UnorderedElementsAre(parent_id1));
+ // Verify that the display surface holds no references while its
+ // cc::CompositorFrame is pending.
+ EXPECT_THAT(GetChildReferences(display_id), IsEmpty());
+
+ // Submitting a cc::CompositorFrame with |parent_id1| should unblock the
+ // display cc::CompositorFrame.
+ parent_support().SubmitCompositorFrame(parent_id1.local_surface_id(),
+ MakeCompositorFrame());
+
+ EXPECT_FALSE(display_surface()->has_deadline());
+ EXPECT_FALSE(display_surface()->HasPendingFrame());
+ EXPECT_TRUE(display_surface()->HasActiveFrame());
+ EXPECT_THAT(display_surface()->activation_dependencies(), IsEmpty());
+}
+
+// This test verifies that a late arriving cc::CompositorFrame activates
+// immediately and does not trigger a new deadline.
+TEST_F(SurfaceSynchronizationTest, LateArrivingDependency) {
+ const SurfaceId display_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+
+ display_support().SubmitCompositorFrame(
+ display_id.local_surface_id(),
+ MakeCompositorFrame({parent_id1}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ EXPECT_TRUE(display_surface()->HasPendingFrame());
+ EXPECT_FALSE(display_surface()->HasActiveFrame());
+ EXPECT_TRUE(display_surface()->has_deadline());
+
+ // Advance BeginFrames to trigger a deadline. This activates the
+ // cc::CompositorFrame submitted above.
+ for (int i = 0; i < 3; ++i) {
+ SendNextBeginFrame();
+ EXPECT_TRUE(display_surface()->has_deadline());
+ }
+ SendNextBeginFrame();
+ EXPECT_FALSE(display_surface()->has_deadline());
+ EXPECT_FALSE(display_surface()->HasPendingFrame());
+ EXPECT_TRUE(display_surface()->HasActiveFrame());
+
+ // A late arriving cc::CompositorFrame should activate immediately without
+ // scheduling a deadline and without waiting for dependencies to resolve.
+ parent_support().SubmitCompositorFrame(
+ parent_id1.local_surface_id(),
+ MakeCompositorFrame({child_id1}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_FALSE(parent_surface()->has_deadline());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+}
+
+// This test verifies that a late arriving cc::CompositorFrame activates
+// immediately along with its subtree and does not trigger a new deadline.
+TEST_F(SurfaceSynchronizationTest, MultiLevelLateArrivingDependency) {
+ const SurfaceId display_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId arbitrary_id = MakeSurfaceId(kArbitraryFrameSink, 1);
+
+ display_support().SubmitCompositorFrame(
+ display_id.local_surface_id(),
+ MakeCompositorFrame({parent_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(display_surface()->HasPendingFrame());
+ EXPECT_FALSE(display_surface()->HasActiveFrame());
+ EXPECT_TRUE(display_surface()->has_deadline());
+
+ // Issue some BeginFrames to trigger the deadline and activate the display's
+ // surface. |parent_id| is now late. Advance BeginFrames to trigger a
+ // deadline.
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(display_surface()->has_deadline());
+ SendNextBeginFrame();
+ }
+ EXPECT_FALSE(display_surface()->HasPendingFrame());
+ EXPECT_TRUE(display_surface()->HasActiveFrame());
+ EXPECT_FALSE(display_surface()->has_deadline());
+
+ // The child surface is not currently causally linked to the display's
+ // surface and so it gets a separate deadline.
+ child_support1().SubmitCompositorFrame(
+ child_id.local_surface_id(),
+ MakeCompositorFrame({arbitrary_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_FALSE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->has_deadline());
+
+ // Submitting a cc::CompositorFrame to the parent surface creates a dependency
+ // chain from the display to the parent to the child, allowing them all to
+ // assume the same deadline. Both the parent and the child are determined to
+ // be late and activate immediately.
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->has_deadline());
+
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->has_deadline());
+}
+
+// This test verifies that CompositorFrames submitted to a surface referenced
+// by a parent cc::CompositorFrame as a fallback will be rejected and ACK'ed
+// immediately.
+TEST_F(SurfaceSynchronizationTest, FallbackSurfacesClosed) {
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ // This is the fallback child surface that the parent holds a reference to.
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ // This is the primary child surface that the parent wants to block on.
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink1, 2);
+
+ // child_support1 submits a cc::CompositorFrame without any dependencies.
+ // DidReceiveCompositorFrameAck should call on immediate activation.
+ // However, resources will not be returned because this frame is a candidate
+ // for display.
+ cc::TransferableResource resource;
+ resource.id = 1337;
+ resource.format = ALPHA_8;
+ resource.filter = 1234;
+ resource.size = gfx::Size(1234, 5678);
+ std::vector<cc::ReturnedResource> returned_resources =
+ cc::TransferableResource::ReturnResources({resource});
+
+ EXPECT_CALL(support_client_, DidReceiveCompositorFrameAck(
+ Eq(std::vector<cc::ReturnedResource>())));
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame(empty_surface_ids(), empty_surface_ids(),
+ {resource}));
+ EXPECT_FALSE(child_surface1()->has_deadline());
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+
+ // The parent is blocked on |child_id2| and references |child_id1|. The
+ // surface corresponding to |child_id1| will not accept new CompositorFrames
+ // while the parent cc::CompositorFrame is blocked.
+ parent_support().SubmitCompositorFrame(
+ parent_id1.local_surface_id(),
+ MakeCompositorFrame({child_id2}, {child_id1},
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+
+ // Resources will be returned immediately because |child_id1|'s surface is
+ // closed.
+ cc::TransferableResource resource2;
+ resource2.id = 1246;
+ resource2.format = ALPHA_8;
+ resource2.filter = 1357;
+ resource2.size = gfx::Size(8765, 4321);
+ std::vector<cc::ReturnedResource> returned_resources2 =
+ cc::TransferableResource::ReturnResources({resource2});
+ EXPECT_CALL(support_client_,
+ DidReceiveCompositorFrameAck(Eq(returned_resources2)));
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame(empty_surface_ids(), empty_surface_ids(),
+ {resource2}));
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+
+ // Advance BeginFrames to trigger a deadline. This activates the
+ // cc::CompositorFrame submitted to the parent.
+ for (int i = 0; i < 3; ++i) {
+ SendNextBeginFrame();
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ }
+ SendNextBeginFrame();
+ EXPECT_FALSE(parent_surface()->has_deadline());
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+
+ // Resources will be returned immediately because |child_id1|'s surface is
+ // closed forever.
+ EXPECT_CALL(support_client_,
+ DidReceiveCompositorFrameAck(Eq(returned_resources2)));
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame(empty_surface_ids(), empty_surface_ids(),
+ {resource2}));
+ testing::Mock::VerifyAndClearExpectations(&support_client_);
+}
+
+// This test verifies that two surface subtrees have independent deadlines.
+TEST_F(SurfaceSynchronizationTest, IndependentDeadlines) {
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+ const SurfaceId arbitrary_id = MakeSurfaceId(kArbitraryFrameSink, 1);
+
+ child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
+ MakeCompositorFrame());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+
+ child_support2().SubmitCompositorFrame(child_id2.local_surface_id(),
+ MakeCompositorFrame());
+ EXPECT_FALSE(child_surface2()->HasPendingFrame());
+ EXPECT_TRUE(child_surface2()->HasActiveFrame());
+
+ parent_support().SubmitCompositorFrame(
+ parent_id1.local_surface_id(),
+ MakeCompositorFrame({child_id1, child_id2}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->has_deadline());
+
+ // Submit another cc::CompositorFrame to |child_id1| that blocks on
+ // |arbitrary_id|.
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame({arbitrary_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->has_deadline());
+
+ // Advance to the next BeginFrame. |child_id1|'s pending Frame should activate
+ // after 3 frames.
+ SendNextBeginFrame();
+
+ // Submit another cc::CompositorFrame to |child_id2| that blocks on
+ // |arbitrary_id|.
+ child_support2().SubmitCompositorFrame(
+ child_id2.local_surface_id(),
+ MakeCompositorFrame({arbitrary_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(child_surface2()->HasPendingFrame());
+ EXPECT_TRUE(child_surface2()->HasActiveFrame());
+ EXPECT_TRUE(child_surface2()->has_deadline());
+
+ // If we issue another two BeginFrames both children should remain blocked.
+ for (int i = 0; i < 2; ++i) {
+ SendNextBeginFrame();
+ EXPECT_TRUE(child_surface1()->has_deadline());
+ EXPECT_TRUE(child_surface2()->has_deadline());
+ }
+
+ // Issuing another BeginFrame should activate the frame in |child_id1| but not
+ // |child_id2|. This verifies that |child_id1| and |child_id2| have different
+ // deadlines.
+ SendNextBeginFrame();
+
+ EXPECT_FALSE(child_surface1()->has_deadline());
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+
+ EXPECT_TRUE(child_surface2()->has_deadline());
+ EXPECT_TRUE(child_surface2()->HasPendingFrame());
+ EXPECT_TRUE(child_surface2()->HasActiveFrame());
+
+ // Issuing another BeginFrame should activate the frame in |child_id2|.
+ SendNextBeginFrame();
+
+ EXPECT_FALSE(child_surface2()->has_deadline());
+ EXPECT_FALSE(child_surface2()->HasPendingFrame());
+ EXPECT_TRUE(child_surface2()->HasActiveFrame());
+}
+
+// This test verifies that a child inherits its deadline from its dependent
+// parent (embedder) surface.
+TEST_F(SurfaceSynchronizationTest, DeadlineInheritance) {
+ const SurfaceId parent_id1 = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId arbitrary_id = MakeSurfaceId(kArbitraryFrameSink, 1);
+
+ parent_support().SubmitCompositorFrame(
+ parent_id1.local_surface_id(),
+ MakeCompositorFrame({child_id1}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->has_deadline());
+
+ // Advance to the next BeginFrame. The parent surface will activate in 3
+ // frames.
+ SendNextBeginFrame();
+
+ child_support1().SubmitCompositorFrame(
+ child_id1.local_surface_id(),
+ MakeCompositorFrame({arbitrary_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_FALSE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->has_deadline());
+
+ // If we issue another three BeginFrames then both the parent and the child
+ // should activate, verifying that the child's deadline is inherited from the
+ // parent.
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_TRUE(child_surface1()->has_deadline());
+ SendNextBeginFrame();
+ }
+
+ // Verify that both the parent and child have activated.
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->has_deadline());
+
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->has_deadline());
+}
+
+// This test verifies that all surfaces within a dependency chain will
+// ultimately inherit the same deadline even if the grandchild is available
+// before the child.
+TEST_F(SurfaceSynchronizationTest, MultiLevelDeadlineInheritance) {
+ const SurfaceId display_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+ const SurfaceId child_id = MakeSurfaceId(kChildFrameSink1, 1);
+ const SurfaceId arbitrary_id = MakeSurfaceId(kArbitraryFrameSink, 1);
+
+ display_support().SubmitCompositorFrame(
+ display_id.local_surface_id(),
+ MakeCompositorFrame({parent_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(display_surface()->HasPendingFrame());
+ EXPECT_FALSE(display_surface()->HasActiveFrame());
+ EXPECT_TRUE(display_surface()->has_deadline());
+
+ // Issue a BeginFrame to move closer to the display's deadline.
+ SendNextBeginFrame();
+
+ // The child surface is not currently causally linked to the display's
+ // surface and so it gets a separate deadline.
+ child_support1().SubmitCompositorFrame(
+ child_id.local_surface_id(),
+ MakeCompositorFrame({arbitrary_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(child_surface1()->HasPendingFrame());
+ EXPECT_FALSE(child_surface1()->HasActiveFrame());
+ EXPECT_TRUE(child_surface1()->has_deadline());
+
+ // Submitting a cc::CompositorFrame to the parent frame creates a dependency
+ // chain from the display to the parent to the child, allowing them all to
+ // assume the same deadline.
+ parent_support().SubmitCompositorFrame(
+ parent_id.local_surface_id(),
+ MakeCompositorFrame({child_id}, empty_surface_ids(),
+ std::vector<cc::TransferableResource>()));
+ EXPECT_TRUE(parent_surface()->HasPendingFrame());
+ EXPECT_FALSE(parent_surface()->HasActiveFrame());
+ EXPECT_TRUE(parent_surface()->has_deadline());
+
+ // Advancing the time by three BeginFrames should activate all the surfaces.
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_TRUE(display_surface()->has_deadline());
+ EXPECT_TRUE(parent_surface()->has_deadline());
+ EXPECT_TRUE(child_surface1()->has_deadline());
+ SendNextBeginFrame();
+ }
+
+ // Verify that all the CompositorFrames have activated.
+ EXPECT_FALSE(display_surface()->HasPendingFrame());
+ EXPECT_TRUE(display_surface()->HasActiveFrame());
+ EXPECT_FALSE(display_surface()->has_deadline());
+
+ EXPECT_FALSE(parent_surface()->HasPendingFrame());
+ EXPECT_TRUE(parent_surface()->HasActiveFrame());
+ EXPECT_FALSE(parent_surface()->has_deadline());
+
+ EXPECT_FALSE(child_surface1()->HasPendingFrame());
+ EXPECT_TRUE(child_surface1()->HasActiveFrame());
+ EXPECT_FALSE(child_surface1()->has_deadline());
+}
+
+} // namespace test
+} // namespace viz
diff --git a/chromium/components/viz/service/hit_test/DEPS b/chromium/components/viz/service/hit_test/DEPS
new file mode 100644
index 00000000000..e207341147d
--- /dev/null
+++ b/chromium/components/viz/service/hit_test/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+components/viz/common",
+ "+cc/surfaces/surface_observer.h",
+ "+services/viz/hit_test/public/interfaces",
+]
diff --git a/chromium/components/viz/service/hit_test/OWNERS b/chromium/components/viz/service/hit_test/OWNERS
new file mode 100644
index 00000000000..3940e4c511d
--- /dev/null
+++ b/chromium/components/viz/service/hit_test/OWNERS
@@ -0,0 +1 @@
+rjkroege@chromium.org \ No newline at end of file
diff --git a/chromium/components/viz/service/hit_test/hit_test_aggregator.cc b/chromium/components/viz/service/hit_test/hit_test_aggregator.cc
new file mode 100644
index 00000000000..72845f5de51
--- /dev/null
+++ b/chromium/components/viz/service/hit_test/hit_test_aggregator.cc
@@ -0,0 +1,213 @@
+// 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(const 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(const SurfaceId& surface_id) {
+ auto search = active_.find(surface_id);
+ if (search == active_.end())
+ return;
+
+ 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
diff --git a/chromium/components/viz/service/hit_test/hit_test_aggregator.h b/chromium/components/viz/service/hit_test/hit_test_aggregator.h
new file mode 100644
index 00000000000..0b49a18201f
--- /dev/null
+++ b/chromium/components/viz/service/hit_test/hit_test_aggregator.h
@@ -0,0 +1,107 @@
+// 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(const 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(const 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_
diff --git a/chromium/components/viz/service/hit_test/hit_test_aggregator_unittest.cc b/chromium/components/viz/service/hit_test/hit_test_aggregator_unittest.cc
new file mode 100644
index 00000000000..4a23b4dca4a
--- /dev/null
+++ b/chromium/components/viz/service/hit_test/hit_test_aggregator_unittest.cc
@@ -0,0 +1,1017 @@
+// 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/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/common/surfaces/surface_id.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace viz {
+
+namespace {
+
+constexpr FrameSinkId kDisplayFrameSink(2, 0);
+
+SurfaceId MakeSurfaceId(const FrameSinkId& frame_sink_id, uint32_t local_id) {
+ return SurfaceId(
+ frame_sink_id,
+ LocalSurfaceId(local_id, base::UnguessableToken::Deserialize(0, 1u)));
+}
+
+} // namespace
+
+class TestHitTestAggregator : public HitTestAggregator {
+ public:
+ TestHitTestAggregator() = default;
+ ~TestHitTestAggregator() = default;
+
+ void CallOnSurfaceWillDraw(SurfaceId surface_id) {
+ OnSurfaceWillDraw(surface_id);
+ }
+ void CallOnSurfaceDiscarded(SurfaceId surface_id) {
+ OnSurfaceDiscarded(surface_id);
+ }
+
+ int Count() {
+ AggregatedHitTestRegion* start =
+ static_cast<AggregatedHitTestRegion*>(read_buffer_.get());
+ AggregatedHitTestRegion* end = start;
+ while (end->child_count != kEndOfList)
+ end++;
+
+ int count = end - start;
+ return count;
+ }
+ int GetPendingCount() { return pending_.size(); }
+ int GetActiveCount() { return active_.size(); }
+ int GetActiveRegionCount() { return active_region_count_; }
+ int GetHitTestRegionListSize() { return read_size_; }
+ AggregatedHitTestRegion* GetRegions() {
+ return static_cast<AggregatedHitTestRegion*>(read_buffer_.get());
+ }
+
+ void Reset() {
+ AggregatedHitTestRegion* regions =
+ static_cast<AggregatedHitTestRegion*>(write_buffer_.get());
+ regions[0].child_count = kEndOfList;
+
+ regions = static_cast<AggregatedHitTestRegion*>(read_buffer_.get());
+ regions[0].child_count = kEndOfList;
+
+ pending_.clear();
+ active_.clear();
+ }
+};
+
+class HitTestAggregatorTest : public testing::Test {
+ public:
+ HitTestAggregatorTest() = default;
+ ~HitTestAggregatorTest() override = default;
+
+ // testing::Test:
+ void SetUp() override {}
+ void TearDown() override { aggregator_.Reset(); }
+
+ TestHitTestAggregator aggregator_;
+
+ // Creates a hit test data element with 8 children recursively to
+ // the specified depth. SurfaceIds are generated in sequential order and
+ // the method returns the next unused id.
+ int CreateAndSubmitHitTestRegionListWith8Children(int id, int depth) {
+ SurfaceId surface_id = MakeSurfaceId(kDisplayFrameSink, id);
+ id++;
+
+ auto hit_test_region_list = mojom::HitTestRegionList::New();
+ hit_test_region_list->surface_id = surface_id;
+ hit_test_region_list->flags = mojom::kHitTestMine;
+ hit_test_region_list->bounds.SetRect(0, 0, 1024, 768);
+
+ for (int i = 0; i < 8; i++) {
+ auto hit_test_region = mojom::HitTestRegion::New();
+ hit_test_region->rect.SetRect(100, 100, 100, 100);
+
+ if (depth > 0) {
+ hit_test_region->flags = mojom::kHitTestChildSurface;
+ hit_test_region->surface_id = MakeSurfaceId(kDisplayFrameSink, id);
+ id = CreateAndSubmitHitTestRegionListWith8Children(id, depth - 1);
+ } else {
+ hit_test_region->flags = mojom::kHitTestMine;
+ }
+ hit_test_region_list->regions.push_back(std::move(hit_test_region));
+ }
+
+ aggregator_.SubmitHitTestRegionList(std::move(hit_test_region_list));
+ return id;
+ }
+};
+
+// TODO(gklassen): Add tests for 3D use cases as suggested by and with
+// input from rjkroege.
+
+// One surface.
+//
+// +----------+
+// | |
+// | |
+// | |
+// +----------+
+//
+TEST_F(HitTestAggregatorTest, OneSurface) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ SurfaceId display_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+
+ auto hit_test_region_list = mojom::HitTestRegionList::New();
+ hit_test_region_list->surface_id = display_surface_id;
+ hit_test_region_list->flags = mojom::kHitTestMine;
+ hit_test_region_list->bounds.SetRect(0, 0, 1024, 768);
+
+ aggregator_.SubmitHitTestRegionList(std::move(hit_test_region_list));
+ EXPECT_EQ(0, aggregator_.Count());
+
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(display_surface_id);
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+
+ aggregator_.Aggregate(display_surface_id);
+ aggregator_.Swap();
+
+ // Expect 1 entry routing all events to the one surface (display root).
+ EXPECT_EQ(1, aggregator_.Count());
+
+ AggregatedHitTestRegion* regions = aggregator_.GetRegions();
+
+ AggregatedHitTestRegion* region = nullptr;
+
+ region = &regions[0];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(display_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
+ EXPECT_EQ(0, region->child_count);
+}
+
+// One opaque embedder with two regions.
+//
+// +e-------------+
+// | +r1-+ +r2--+ |
+// | | | | | |
+// | | | | | |
+// | +---+ +----+ |
+// +--------------+
+//
+TEST_F(HitTestAggregatorTest, OneEmbedderTwoRegions) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ SurfaceId e_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+
+ auto e_hit_test_data = mojom::HitTestRegionList::New();
+ e_hit_test_data->surface_id = e_surface_id;
+ e_hit_test_data->flags = mojom::kHitTestMine;
+ e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
+
+ auto e_hit_test_region_r1 = mojom::HitTestRegion::New();
+ e_hit_test_region_r1->flags = mojom::kHitTestMine;
+ e_hit_test_region_r1->rect.SetRect(100, 100, 200, 400);
+
+ auto e_hit_test_region_r2 = mojom::HitTestRegion::New();
+ e_hit_test_region_r2->flags = mojom::kHitTestMine;
+ e_hit_test_region_r2->rect.SetRect(400, 100, 300, 400);
+
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_r1));
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_r2));
+
+ // Submit mojom::HitTestRegionList.
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(e_hit_test_data));
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+
+ // Add Surfaces to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(e_surface_id);
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+
+ // Aggregate and swap.
+
+ aggregator_.Aggregate(e_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+
+ aggregator_.Swap();
+ EXPECT_EQ(3, aggregator_.Count());
+
+ AggregatedHitTestRegion* regions = aggregator_.GetRegions();
+
+ AggregatedHitTestRegion* region = nullptr;
+
+ region = &regions[0];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
+ EXPECT_EQ(2, region->child_count);
+
+ region = &regions[1];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(gfx::Rect(100, 100, 200, 400), region->rect);
+ EXPECT_EQ(0, region->child_count);
+
+ region = &regions[2];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(gfx::Rect(400, 100, 300, 400), region->rect);
+ EXPECT_EQ(0, region->child_count);
+}
+
+// One embedder with two children.
+//
+// +e-------------+
+// | +c1-+ +c2--+ |
+// | | | | | |
+// | | | | | |
+// | +---+ +----+ |
+// +--------------+
+//
+
+TEST_F(HitTestAggregatorTest, OneEmbedderTwoChildren) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ SurfaceId e_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ SurfaceId c1_surface_id = MakeSurfaceId(kDisplayFrameSink, 2);
+ SurfaceId c2_surface_id = MakeSurfaceId(kDisplayFrameSink, 3);
+
+ auto e_hit_test_data = mojom::HitTestRegionList::New();
+ e_hit_test_data->surface_id = e_surface_id;
+ e_hit_test_data->flags = mojom::kHitTestMine;
+ e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
+
+ auto e_hit_test_region_c1 = mojom::HitTestRegion::New();
+ e_hit_test_region_c1->flags = mojom::kHitTestChildSurface;
+ e_hit_test_region_c1->surface_id = c1_surface_id;
+ e_hit_test_region_c1->rect.SetRect(100, 100, 200, 300);
+
+ auto e_hit_test_region_c2 = mojom::HitTestRegion::New();
+ e_hit_test_region_c2->flags = mojom::kHitTestChildSurface;
+ e_hit_test_region_c2->surface_id = c2_surface_id;
+ e_hit_test_region_c2->rect.SetRect(400, 100, 400, 300);
+
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c1));
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c2));
+
+ auto c1_hit_test_data = mojom::HitTestRegionList::New();
+ c1_hit_test_data->surface_id = c1_surface_id;
+
+ auto c2_hit_test_data = mojom::HitTestRegionList::New();
+ c2_hit_test_data->surface_id = c2_surface_id;
+
+ // Submit in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c1_hit_test_data));
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(e_hit_test_data));
+ EXPECT_EQ(2, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c2_hit_test_data));
+ EXPECT_EQ(3, aggregator_.GetPendingCount());
+
+ // Surfaces added to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c2_surface_id);
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c1_surface_id);
+ EXPECT_EQ(2, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(e_surface_id);
+ EXPECT_EQ(3, aggregator_.GetActiveCount());
+
+ // Aggregate and swap.
+
+ aggregator_.Aggregate(e_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+
+ aggregator_.Swap();
+
+ EXPECT_EQ(3, aggregator_.Count());
+
+ AggregatedHitTestRegion* regions = aggregator_.GetRegions();
+
+ AggregatedHitTestRegion* region = nullptr;
+
+ region = &regions[0];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
+ EXPECT_EQ(2, region->child_count);
+
+ region = &regions[1];
+ EXPECT_EQ(mojom::kHitTestChildSurface, region->flags);
+ EXPECT_EQ(c1_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(100, 100, 200, 300), region->rect);
+ EXPECT_EQ(0, region->child_count);
+
+ region = &regions[2];
+ EXPECT_EQ(mojom::kHitTestChildSurface, region->flags);
+ EXPECT_EQ(c2_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(400, 100, 400, 300), region->rect);
+ EXPECT_EQ(0, region->child_count);
+}
+
+// Occluded child frame (OOPIF).
+//
+// +e-----------+
+// | +c--+ |
+// | | +div-+ |
+// | | | | |
+// | | +----+ |
+// | +---+ |
+// +------------+
+//
+
+TEST_F(HitTestAggregatorTest, OccludedChildFrame) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ SurfaceId e_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ SurfaceId c_surface_id = MakeSurfaceId(kDisplayFrameSink, 2);
+
+ auto e_hit_test_data = mojom::HitTestRegionList::New();
+ e_hit_test_data->surface_id = e_surface_id;
+ e_hit_test_data->flags = mojom::kHitTestMine;
+ e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
+
+ auto e_hit_test_region_div = mojom::HitTestRegion::New();
+ e_hit_test_region_div->flags = mojom::kHitTestMine;
+ e_hit_test_region_div->surface_id = e_surface_id;
+ e_hit_test_region_div->rect.SetRect(200, 200, 300, 200);
+
+ auto e_hit_test_region_c = mojom::HitTestRegion::New();
+ e_hit_test_region_c->flags = mojom::kHitTestChildSurface;
+ e_hit_test_region_c->surface_id = c_surface_id;
+ e_hit_test_region_c->rect.SetRect(100, 100, 200, 500);
+
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_div));
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c));
+
+ auto c_hit_test_data = mojom::HitTestRegionList::New();
+ c_hit_test_data->surface_id = c_surface_id;
+ c_hit_test_data->flags = mojom::kHitTestMine;
+ c_hit_test_data->bounds.SetRect(0, 0, 200, 500);
+
+ // Submit in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c_hit_test_data));
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(e_hit_test_data));
+ EXPECT_EQ(2, aggregator_.GetPendingCount());
+
+ // Surfaces added to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(e_surface_id);
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c_surface_id);
+ EXPECT_EQ(2, aggregator_.GetActiveCount());
+
+ // Aggregate and swap.
+
+ aggregator_.Aggregate(e_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+
+ aggregator_.Swap();
+
+ EXPECT_EQ(3, aggregator_.Count());
+
+ AggregatedHitTestRegion* regions = aggregator_.GetRegions();
+
+ AggregatedHitTestRegion* region = nullptr;
+
+ region = &regions[0];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
+ EXPECT_EQ(2, region->child_count);
+
+ region = &regions[1];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(200, 200, 300, 200), region->rect);
+ EXPECT_EQ(0, region->child_count);
+
+ region = &regions[2];
+ EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestMine);
+ EXPECT_EQ(c_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(100, 100, 200, 500), region->rect);
+ EXPECT_EQ(0, region->child_count);
+}
+
+// Foreground child frame (OOPIF).
+// Same as the previous test except the child is foreground.
+//
+// +e-----------+
+// | +c--+ |
+// | | |div-+ |
+// | | | | |
+// | | |----+ |
+// | +---+ |
+// +------------+
+//
+
+TEST_F(HitTestAggregatorTest, ForegroundChildFrame) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ SurfaceId e_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ SurfaceId c_surface_id = MakeSurfaceId(kDisplayFrameSink, 2);
+
+ auto e_hit_test_data = mojom::HitTestRegionList::New();
+ e_hit_test_data->surface_id = e_surface_id;
+ e_hit_test_data->flags = mojom::kHitTestMine;
+ e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
+
+ auto e_hit_test_region_div = mojom::HitTestRegion::New();
+ e_hit_test_region_div->flags = mojom::kHitTestMine;
+ e_hit_test_region_div->surface_id = e_surface_id;
+ e_hit_test_region_div->rect.SetRect(200, 200, 300, 200);
+
+ auto e_hit_test_region_c = mojom::HitTestRegion::New();
+ e_hit_test_region_c->flags = mojom::kHitTestChildSurface;
+ e_hit_test_region_c->surface_id = c_surface_id;
+ e_hit_test_region_c->rect.SetRect(100, 100, 200, 500);
+
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c));
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_div));
+
+ auto c_hit_test_data = mojom::HitTestRegionList::New();
+ c_hit_test_data->surface_id = c_surface_id;
+ c_hit_test_data->flags = mojom::kHitTestMine;
+ c_hit_test_data->bounds.SetRect(0, 0, 200, 500);
+
+ // Submit in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c_hit_test_data));
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(e_hit_test_data));
+ EXPECT_EQ(2, aggregator_.GetPendingCount());
+
+ // Surfaces added to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(e_surface_id);
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c_surface_id);
+ EXPECT_EQ(2, aggregator_.GetActiveCount());
+
+ // Aggregate and swap.
+
+ aggregator_.Aggregate(e_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+
+ aggregator_.Swap();
+
+ EXPECT_EQ(3, aggregator_.Count());
+
+ AggregatedHitTestRegion* regions = aggregator_.GetRegions();
+
+ AggregatedHitTestRegion* region = nullptr;
+
+ region = &regions[0];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
+ EXPECT_EQ(2, region->child_count);
+
+ region = &regions[1];
+ EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestMine);
+ EXPECT_EQ(c_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(100, 100, 200, 500), region->rect);
+ EXPECT_EQ(0, region->child_count);
+
+ region = &regions[2];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(200, 200, 300, 200), region->rect);
+ EXPECT_EQ(0, region->child_count);
+}
+
+// One embedder with a clipped child with a tab and transparent background.
+//
+// +e-------------+
+// | +c---------| Point maps to
+// | 1 |+a--+ | ----- -------
+// | || 2 | 3 | 1 e
+// | |+b--------| 2 a
+// | || | 3 e (transparent area in c)
+// | || 4 | 4 b
+// +--------------+
+//
+
+TEST_F(HitTestAggregatorTest, ClippedChildWithTabAndTransparentBackground) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ SurfaceId e_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ SurfaceId c_surface_id = MakeSurfaceId(kDisplayFrameSink, 2);
+ SurfaceId a_surface_id = MakeSurfaceId(kDisplayFrameSink, 3);
+ SurfaceId b_surface_id = MakeSurfaceId(kDisplayFrameSink, 4);
+
+ auto e_hit_test_data = mojom::HitTestRegionList::New();
+ e_hit_test_data->surface_id = e_surface_id;
+ e_hit_test_data->flags = mojom::kHitTestMine;
+ e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
+
+ auto e_hit_test_region_c = mojom::HitTestRegion::New();
+ e_hit_test_region_c->flags = mojom::kHitTestChildSurface;
+ e_hit_test_region_c->surface_id = c_surface_id;
+ e_hit_test_region_c->rect.SetRect(200, 100, 1600, 800);
+ e_hit_test_region_c->transform.Translate(200, 100);
+
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c));
+
+ auto c_hit_test_data = mojom::HitTestRegionList::New();
+ c_hit_test_data->surface_id = c_surface_id;
+ c_hit_test_data->flags = mojom::kHitTestIgnore;
+ c_hit_test_data->bounds.SetRect(0, 0, 1600, 800);
+
+ auto c_hit_test_region_a = mojom::HitTestRegion::New();
+ c_hit_test_region_a->flags = mojom::kHitTestChildSurface;
+ c_hit_test_region_a->surface_id = a_surface_id;
+ c_hit_test_region_a->rect.SetRect(0, 0, 200, 100);
+
+ auto c_hit_test_region_b = mojom::HitTestRegion::New();
+ c_hit_test_region_b->flags = mojom::kHitTestChildSurface;
+ c_hit_test_region_b->surface_id = b_surface_id;
+ c_hit_test_region_b->rect.SetRect(0, 100, 800, 600);
+
+ c_hit_test_data->regions.push_back(std::move(c_hit_test_region_a));
+ c_hit_test_data->regions.push_back(std::move(c_hit_test_region_b));
+
+ auto a_hit_test_data = mojom::HitTestRegionList::New();
+ a_hit_test_data->surface_id = a_surface_id;
+ a_hit_test_data->flags = mojom::kHitTestMine;
+ a_hit_test_data->bounds.SetRect(0, 0, 200, 100);
+
+ auto b_hit_test_data = mojom::HitTestRegionList::New();
+ b_hit_test_data->surface_id = b_surface_id;
+ b_hit_test_data->flags = mojom::kHitTestMine;
+ b_hit_test_data->bounds.SetRect(0, 100, 800, 600);
+
+ // Submit in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c_hit_test_data));
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(a_hit_test_data));
+ EXPECT_EQ(2, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(b_hit_test_data));
+ EXPECT_EQ(3, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(e_hit_test_data));
+ EXPECT_EQ(4, aggregator_.GetPendingCount());
+
+ // Surfaces added to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c_surface_id);
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(e_surface_id);
+ EXPECT_EQ(2, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(b_surface_id);
+ EXPECT_EQ(3, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(a_surface_id);
+ EXPECT_EQ(4, aggregator_.GetActiveCount());
+
+ // Aggregate and swap.
+
+ aggregator_.Aggregate(e_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+
+ aggregator_.Swap();
+
+ EXPECT_EQ(4, aggregator_.Count());
+
+ AggregatedHitTestRegion* regions = aggregator_.GetRegions();
+
+ AggregatedHitTestRegion* region = nullptr;
+
+ region = &regions[0];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
+ EXPECT_EQ(3, region->child_count);
+
+ region = &regions[1];
+ EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestIgnore);
+ EXPECT_EQ(c_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(200, 100, 1600, 800), region->rect);
+ EXPECT_EQ(2, region->child_count);
+
+ gfx::Point point(300, 300);
+ region->transform.TransformPointReverse(&point);
+ EXPECT_TRUE(point == gfx::Point(100, 200));
+
+ region = &regions[2];
+ EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestMine);
+ EXPECT_EQ(a_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 200, 100), region->rect);
+ EXPECT_EQ(0, region->child_count);
+
+ region = &regions[3];
+ EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestMine);
+ EXPECT_EQ(b_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 100, 800, 600), region->rect);
+ EXPECT_EQ(0, region->child_count);
+}
+
+// Three children deep.
+//
+// +e------------+
+// | +c1-------+ |
+// | | +c2---+ | |
+// | | | +c3-| | |
+// | | | | | | |
+// | | | +---| | |
+// | | +-----+ | |
+// | +---------+ |
+// +-------------+
+//
+
+TEST_F(HitTestAggregatorTest, ThreeChildrenDeep) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ SurfaceId e_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ SurfaceId c1_surface_id = MakeSurfaceId(kDisplayFrameSink, 2);
+ SurfaceId c2_surface_id = MakeSurfaceId(kDisplayFrameSink, 3);
+ SurfaceId c3_surface_id = MakeSurfaceId(kDisplayFrameSink, 4);
+
+ auto e_hit_test_data = mojom::HitTestRegionList::New();
+ e_hit_test_data->surface_id = e_surface_id;
+ e_hit_test_data->flags = mojom::kHitTestMine;
+ e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
+
+ auto e_hit_test_region_c1 = mojom::HitTestRegion::New();
+ e_hit_test_region_c1->flags = mojom::kHitTestChildSurface;
+ e_hit_test_region_c1->surface_id = c1_surface_id;
+ e_hit_test_region_c1->rect.SetRect(100, 100, 700, 700);
+
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c1));
+
+ auto c1_hit_test_data = mojom::HitTestRegionList::New();
+ c1_hit_test_data->surface_id = c1_surface_id;
+ c1_hit_test_data->flags = mojom::kHitTestMine;
+ c1_hit_test_data->bounds.SetRect(0, 0, 600, 600);
+
+ auto c1_hit_test_region_c2 = mojom::HitTestRegion::New();
+ c1_hit_test_region_c2->flags = mojom::kHitTestChildSurface;
+ c1_hit_test_region_c2->surface_id = c2_surface_id;
+ c1_hit_test_region_c2->rect.SetRect(100, 100, 500, 500);
+
+ c1_hit_test_data->regions.push_back(std::move(c1_hit_test_region_c2));
+
+ auto c2_hit_test_data = mojom::HitTestRegionList::New();
+ c2_hit_test_data->surface_id = c2_surface_id;
+ c2_hit_test_data->flags = mojom::kHitTestMine;
+ c2_hit_test_data->bounds.SetRect(0, 0, 400, 400);
+
+ auto c2_hit_test_region_c3 = mojom::HitTestRegion::New();
+ c2_hit_test_region_c3->flags = mojom::kHitTestChildSurface;
+ c2_hit_test_region_c3->surface_id = c3_surface_id;
+ c2_hit_test_region_c3->rect.SetRect(100, 100, 300, 300);
+
+ c2_hit_test_data->regions.push_back(std::move(c2_hit_test_region_c3));
+
+ auto c3_hit_test_data = mojom::HitTestRegionList::New();
+ c3_hit_test_data->surface_id = c3_surface_id;
+ c3_hit_test_data->flags = mojom::kHitTestMine;
+ c3_hit_test_data->bounds.SetRect(0, 0, 200, 200);
+
+ // Submit in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c1_hit_test_data));
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c3_hit_test_data));
+ EXPECT_EQ(2, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(e_hit_test_data));
+ EXPECT_EQ(3, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c2_hit_test_data));
+ EXPECT_EQ(4, aggregator_.GetPendingCount());
+
+ // Surfaces added to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c2_surface_id);
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c1_surface_id);
+ EXPECT_EQ(2, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(e_surface_id);
+ EXPECT_EQ(3, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c3_surface_id);
+ EXPECT_EQ(4, aggregator_.GetActiveCount());
+
+ // Aggregate and swap.
+
+ aggregator_.Aggregate(e_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+
+ aggregator_.Swap();
+
+ EXPECT_EQ(4, aggregator_.Count());
+
+ AggregatedHitTestRegion* regions = aggregator_.GetRegions();
+
+ AggregatedHitTestRegion* region = nullptr;
+
+ region = &regions[0];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
+ EXPECT_EQ(3, region->child_count);
+
+ region = &regions[1];
+ EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestMine);
+ EXPECT_EQ(c1_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(100, 100, 700, 700), region->rect);
+ EXPECT_EQ(2, region->child_count);
+
+ region = &regions[2];
+ EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestMine);
+ EXPECT_EQ(c2_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(100, 100, 500, 500), region->rect);
+ EXPECT_EQ(1, region->child_count);
+
+ region = &regions[3];
+ EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestMine);
+ EXPECT_EQ(c3_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(100, 100, 300, 300), region->rect);
+ EXPECT_EQ(0, region->child_count);
+}
+
+// Missing / late child.
+//
+// +e-----------+
+// | +c--+ |
+// | | |div-+ |
+// | | | | |
+// | | |----+ |
+// | +---+ |
+// +------------+
+//
+
+TEST_F(HitTestAggregatorTest, MissingChildFrame) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ SurfaceId e_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ SurfaceId c_surface_id = MakeSurfaceId(kDisplayFrameSink, 2);
+
+ auto e_hit_test_data = mojom::HitTestRegionList::New();
+ e_hit_test_data->surface_id = e_surface_id;
+ e_hit_test_data->flags = mojom::kHitTestMine;
+ e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
+
+ auto e_hit_test_region_div = mojom::HitTestRegion::New();
+ e_hit_test_region_div->flags = mojom::kHitTestMine;
+ e_hit_test_region_div->surface_id = e_surface_id;
+ e_hit_test_region_div->rect.SetRect(200, 200, 300, 200);
+
+ auto e_hit_test_region_c = mojom::HitTestRegion::New();
+ e_hit_test_region_c->flags = mojom::kHitTestChildSurface;
+ e_hit_test_region_c->surface_id = c_surface_id;
+ e_hit_test_region_c->rect.SetRect(100, 100, 200, 500);
+
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c));
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_div));
+
+ auto c_hit_test_data = mojom::HitTestRegionList::New();
+ c_hit_test_data->surface_id = c_surface_id;
+ c_hit_test_data->flags = mojom::kHitTestMine;
+ c_hit_test_data->bounds.SetRect(0, 0, 200, 500);
+
+ // Submit in unexpected order, but not the child.
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(e_hit_test_data));
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+
+ // Surfaces added to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(e_surface_id);
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+
+ // Aggregate and swap.
+
+ aggregator_.Aggregate(e_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+
+ aggregator_.Swap();
+
+ EXPECT_EQ(2, aggregator_.Count());
+
+ AggregatedHitTestRegion* regions = aggregator_.GetRegions();
+
+ AggregatedHitTestRegion* region = nullptr;
+
+ region = &regions[0];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
+ EXPECT_EQ(1, region->child_count);
+
+ // Child would exist here but it was not included in the Display Frame.
+
+ region = &regions[1];
+ EXPECT_EQ(mojom::kHitTestMine, region->flags);
+ EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
+ EXPECT_EQ(gfx::Rect(200, 200, 300, 200), region->rect);
+ EXPECT_EQ(0, region->child_count);
+}
+
+// Exceed limits to ensure that bounds and resize work.
+//
+// A tree of embedders each with 8 children and 4 levels deep = 4096 regions.
+// This will exceed initial allocation and force a resize.
+//
+// +e--------------------------------------------------------+
+// | +c1----------++c2----------++c3----------++c4----------+|
+// | | +c1--------|| +c1--------|| +c1--------|| +c1--------||
+// | | | +c1-++c2-|| | +c1-++c2-|| | +c1-++c2-|| | +c1-++c2-||
+// | | | | || || | | || || | | || || | | || ||
+// | | | +---++---|| | +---++---|| | +---++---|| | +---++---||
+// | +------------++------------++------------++------------+|
+// | +c5----------++c6----------++c7----------++c8----------+|
+// | | +c1--------|| +c1--------|| +c1--------|| +c1--------||
+// | | | +c1-++c2-|| | +c1-++c2-|| | +c1-++c2-|| | +c1-++c2-||
+// | | | | || || | | || || | | || || | | || ||
+// | | | +---++---|| | +---++---|| | +---++---|| | +---++---||
+// | +------------++------------++------------++------------+|
+// +---------------------------------------------------------+
+//
+
+TEST_F(HitTestAggregatorTest, ExceedLimits) {
+ EXPECT_EQ(0, aggregator_.Count());
+
+ EXPECT_LT(aggregator_.GetHitTestRegionListSize(), 4096);
+
+ SurfaceId display_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+
+ int next_surface_id = CreateAndSubmitHitTestRegionListWith8Children(1, 3);
+ int surface_count = next_surface_id - 1;
+
+ EXPECT_EQ(surface_count, aggregator_.GetPendingCount());
+
+ // Mark Surfaces as added to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ for (int i = 1; i <= surface_count; i++) {
+ SurfaceId surface_id = MakeSurfaceId(kDisplayFrameSink, i);
+ aggregator_.CallOnSurfaceWillDraw(surface_id);
+ }
+
+ EXPECT_EQ(surface_count, aggregator_.GetActiveCount());
+
+ // Aggregate and swap.
+ aggregator_.Aggregate(display_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+
+ aggregator_.Swap();
+
+ // Expect 4680 regions:
+ // 8 children 4 levels deep 8*8*8*8 is 4096
+ // 1 region for each embedder/surface + 584
+ // 1 root + 1
+ // -----
+ // 4681.
+ EXPECT_EQ(4681, aggregator_.Count());
+
+ EXPECT_GE(aggregator_.GetHitTestRegionListSize(), 4681);
+}
+
+TEST_F(HitTestAggregatorTest, ActiveRegionCount) {
+ EXPECT_EQ(0, aggregator_.GetActiveRegionCount());
+
+ SurfaceId e_surface_id = MakeSurfaceId(kDisplayFrameSink, 1);
+ SurfaceId c_surface_id = MakeSurfaceId(kDisplayFrameSink, 2);
+
+ auto e_hit_test_data = mojom::HitTestRegionList::New();
+ e_hit_test_data->surface_id = e_surface_id;
+ e_hit_test_data->flags = mojom::kHitTestMine;
+ e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
+
+ auto e_hit_test_region_div = mojom::HitTestRegion::New();
+ e_hit_test_region_div->flags = mojom::kHitTestMine;
+ e_hit_test_region_div->surface_id = e_surface_id;
+ e_hit_test_region_div->rect.SetRect(200, 200, 300, 200);
+
+ auto e_hit_test_region_c = mojom::HitTestRegion::New();
+ e_hit_test_region_c->flags = mojom::kHitTestChildSurface;
+ e_hit_test_region_c->surface_id = c_surface_id;
+ e_hit_test_region_c->rect.SetRect(100, 100, 200, 500);
+
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c));
+ e_hit_test_data->regions.push_back(std::move(e_hit_test_region_div));
+
+ auto c_hit_test_data = mojom::HitTestRegionList::New();
+ c_hit_test_data->surface_id = c_surface_id;
+ c_hit_test_data->flags = mojom::kHitTestMine;
+ c_hit_test_data->bounds.SetRect(0, 0, 200, 500);
+
+ EXPECT_EQ(0, aggregator_.GetActiveRegionCount());
+
+ // Submit in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(c_hit_test_data));
+ EXPECT_EQ(1, aggregator_.GetPendingCount());
+
+ aggregator_.SubmitHitTestRegionList(std::move(e_hit_test_data));
+ EXPECT_EQ(2, aggregator_.GetPendingCount());
+
+ EXPECT_EQ(0, aggregator_.GetActiveRegionCount());
+
+ // Surfaces added to DisplayFrame in unexpected order.
+
+ EXPECT_EQ(0, aggregator_.Count());
+ EXPECT_EQ(0, aggregator_.GetActiveCount());
+
+ aggregator_.CallOnSurfaceWillDraw(e_surface_id);
+ EXPECT_EQ(1, aggregator_.GetActiveCount());
+ EXPECT_EQ(2, aggregator_.GetActiveRegionCount());
+
+ aggregator_.CallOnSurfaceWillDraw(c_surface_id);
+ EXPECT_EQ(2, aggregator_.GetActiveCount());
+ EXPECT_EQ(2, aggregator_.GetActiveRegionCount());
+
+ // Aggregate and swap.
+
+ aggregator_.Aggregate(e_surface_id);
+ EXPECT_EQ(0, aggregator_.Count());
+ EXPECT_EQ(2, aggregator_.GetActiveRegionCount());
+
+ aggregator_.Swap();
+
+ EXPECT_EQ(3, aggregator_.Count());
+ EXPECT_EQ(2, aggregator_.GetActiveRegionCount());
+
+ // Discard Surface and ensure active count goes down.
+
+ aggregator_.CallOnSurfaceDiscarded(c_surface_id);
+ EXPECT_EQ(2, aggregator_.GetActiveRegionCount());
+
+ aggregator_.CallOnSurfaceDiscarded(e_surface_id);
+ EXPECT_EQ(0, aggregator_.GetActiveRegionCount());
+}
+
+} // namespace viz
diff --git a/chromium/components/viz/service/viz_service_export.h b/chromium/components/viz/service/viz_service_export.h
new file mode 100644
index 00000000000..e2d6a9e9688
--- /dev/null
+++ b/chromium/components/viz/service/viz_service_export.h
@@ -0,0 +1,29 @@
+// 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_VIZ_SERVICE_EXPORT_H_
+#define COMPONENTS_VIZ_SERVICE_VIZ_SERVICE_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(VIZ_SERVICE_IMPLEMENTATION)
+#define VIZ_SERVICE_EXPORT __declspec(dllexport)
+#else
+#define VIZ_SERVICE_EXPORT __declspec(dllimport)
+#endif // defined(VIZ_SERVICE_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(VIZ_SERVICE_IMPLEMENTATION)
+#define VIZ_SERVICE_EXPORT __attribute__((visibility("default")))
+#else
+#define VIZ_SERVICE_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define VIZ_SERVICE_EXPORT
+#endif
+
+#endif // COMPONENTS_VIZ_SERVICE_VIZ_SERVICE_EXPORT_H_