diff options
Diffstat (limited to 'chromium/components/viz/service')
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, ©_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(©_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, ©_requests, ©_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(©_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, ©_requests, ©_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, + ¤t_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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[1]; + EXPECT_EQ(mojom::kHitTestMine, region->flags); + EXPECT_EQ(gfx::Rect(100, 100, 200, 400), region->rect); + EXPECT_EQ(0, region->child_count); + + region = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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 = ®ions[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_ |