blob: 7deb1b50e77efb6813e82139effb1ba1df9bf6eb [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// 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 <limits>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/null_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "cc/base/features.h"
#include "cc/base/math_util.h"
#include "cc/test/scheduler_test_common.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "components/viz/common/quads/aggregated_render_pass.h"
#include "components/viz/common/quads/aggregated_render_pass_draw_quad.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "components/viz/common/surfaces/region_capture_bounds.h"
#include "components/viz/common/surfaces/subtree_capture_id.h"
#include "components/viz/common/switches.h"
#include "components/viz/service/display/aggregated_frame.h"
#include "components/viz/service/display/delegated_ink_point_renderer_skia.h"
#include "components/viz/service/display/delegated_ink_trail_data.h"
#include "components/viz/service/display/direct_renderer.h"
#include "components/viz/service/display/display_client.h"
#include "components/viz/service/display/display_scheduler.h"
#include "components/viz/service/display/overlay_processor_stub.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/service/surfaces/surface_manager.h"
#include "components/viz/test/compositor_frame_helpers.h"
#include "components/viz/test/delegated_ink_point_renderer_skia_for_test.h"
#include "components/viz/test/fake_output_surface.h"
#include "components/viz/test/fake_skia_output_surface.h"
#include "components/viz/test/mock_compositor_frame_sink_client.h"
#include "components/viz/test/test_gles2_interface.h"
#include "components/viz/test/test_surface_id_allocator.h"
#include "components/viz/test/viz_test_suite.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/common/swap_buffers_complete_params.h"
#include "gpu/command_buffer/service/scheduler.h"
#include "gpu/command_buffer/service/shared_image/shared_image_manager.h"
#include "gpu/command_buffer/service/sync_point_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/gfx/delegated_ink_metadata.h"
#include "ui/gfx/delegated_ink_point.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/mojom/delegated_ink_point_renderer.mojom.h"
#include "ui/gfx/overlay_transform.h"
using testing::_;
using testing::AnyNumber;
namespace viz {
namespace {
static constexpr FrameSinkId kArbitraryFrameSinkId(3, 3);
static constexpr FrameSinkId kAnotherFrameSinkId(4, 4);
static constexpr FrameSinkId kAnotherFrameSinkId2(5, 5);
const uint64_t kBeginFrameSourceId = 1337;
class TestSoftwareOutputDevice : public SoftwareOutputDevice {
public:
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(BeginFrameSource* begin_frame_source,
base::SingleThreadTaskRunner* task_runner)
: DisplayScheduler(begin_frame_source,
task_runner,
PendingSwapParams(1)) {}
~TestDisplayScheduler() override = default;
void OnDisplayDamaged(SurfaceId surface_id) override {
damaged_ = true;
needs_draw_ = true;
}
void DidSwapBuffers() override { swapped_ = true; }
void ResetDamageForTest() { damaged_ = false; }
bool damaged() const { return damaged_; }
bool swapped() const { return swapped_; }
void reset_swapped_for_test() { swapped_ = false; }
private:
bool damaged_ = false;
bool swapped_ = false;
};
class StubDisplayClient : public DisplayClient {
public:
void DisplayOutputSurfaceLost() override {}
void DisplayWillDrawAndSwap(
bool will_draw_and_swap,
AggregatedRenderPassList* render_passes) override {}
void DisplayDidDrawAndSwap() override {}
void DisplayDidReceiveCALayerParams(
const gfx::CALayerParams& ca_layer_params) override {}
void DisplayDidCompleteSwapWithSize(const gfx::Size& pixel_size) override {}
void DisplayAddChildWindowToBrowser(
gpu::SurfaceHandle child_window) override {}
void SetWideColorEnabled(bool enabled) override {}
};
void CopyCallback(bool* called,
base::OnceClosure finished,
std::unique_ptr<CopyOutputResult> result) {
*called = true;
std::move(finished).Run();
}
gfx::SwapTimings GetTestSwapTimings() {
base::TimeTicks now = base::TimeTicks::Now();
return gfx::SwapTimings{now, now};
}
} // namespace
class DisplayTest : public testing::Test {
public:
DisplayTest()
: manager_(FrameSinkManagerImpl::InitParams()),
support_(
std::make_unique<CompositorFrameSinkSupport>(nullptr,
&manager_,
kArbitraryFrameSinkId,
true /* is_root */)),
task_runner_(new base::NullTaskRunner),
client_(std::make_unique<StubDisplayClient>()) {}
~DisplayTest() override = default;
void SetUpSoftwareDisplay(const RendererSettings& settings) {
std::unique_ptr<FakeSoftwareOutputSurface> output_surface;
auto device = std::make_unique<TestSoftwareOutputDevice>();
software_output_device_ = device.get();
output_surface =
std::make_unique<FakeSoftwareOutputSurface>(std::move(device));
output_surface_ = output_surface.get();
CreateDisplaySchedulerAndDisplay(settings, kArbitraryFrameSinkId,
std::move(output_surface));
}
void SetUpGpuDisplay(const RendererSettings& settings) {
scoped_refptr<TestContextProvider> provider = TestContextProvider::Create();
provider->BindToCurrentSequence();
std::unique_ptr<FakeSkiaOutputSurface> skia_output_surface =
FakeSkiaOutputSurface::Create3d(std::move(provider));
skia_output_surface_ = skia_output_surface.get();
CreateDisplaySchedulerAndDisplay(settings, kArbitraryFrameSinkId,
std::move(skia_output_surface));
}
void CreateDisplaySchedulerAndDisplay(
const RendererSettings& settings,
const FrameSinkId& frame_sink_id,
std::unique_ptr<OutputSurface> output_surface) {
begin_frame_source_ = std::make_unique<StubBeginFrameSource>();
auto scheduler = std::make_unique<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<OutputSurface> output_surface) {
auto overlay_processor = std::make_unique<OverlayProcessorStub>();
// Normally display will need to take ownership of a
// DisplayCompositorMemoryAndTaskController in order to keep it alive to
// share between the output surface and the overlay processor. In this case
// the overlay processor is a stub and the output surface is test only as
// well, so there is no need to pass in a real
// DisplayCompositorMemoryAndTaskController.
auto display = std::make_unique<Display>(
&shared_image_manager_, &gpu_scheduler_, settings, &debug_settings_,
frame_sink_id, nullptr /* DisplayCompositorMemoryAndTaskController */,
std::move(output_surface), std::move(overlay_processor),
std::move(scheduler), task_runner_);
display->SetVisible(true);
return display;
}
bool ShouldSendBeginFrame(CompositorFrameSinkSupport* support,
base::TimeTicks frame_time) {
return support->ShouldSendBeginFrame(BeginFrameId(999, 999), frame_time,
base::Seconds(0));
}
void UpdateBeginFrameTime(CompositorFrameSinkSupport* support,
base::TimeTicks frame_time) {
support->last_frame_time_ = frame_time;
support->frame_timing_details_.clear();
}
protected:
void TearDown() override {
// Only call UnregisterBeginFrameSource if SetupDisplay has been called.
if (begin_frame_source_)
manager_.UnregisterBeginFrameSource(begin_frame_source_.get());
}
void SubmitCompositorFrame(CompositorRenderPassList* pass_list,
const LocalSurfaceId& local_surface_id) {
CompositorFrame frame = CompositorFrameBuilder()
.SetRenderPassList(std::move(*pass_list))
.Build();
pass_list->clear();
support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
}
void ResetDamageForTest() { scheduler_->ResetDamageForTest(); }
void RunUntilIdle() { VizTestSuite::RunUntilIdle(); }
void LatencyInfoCapTest(bool over_capacity);
size_t pending_presentation_group_timings_size() {
return display_->pending_presentation_group_timings_.size();
}
DebugRendererSettings debug_settings_;
gpu::SharedImageManager shared_image_manager_;
gpu::SyncPointManager sync_point_manager_;
gpu::Scheduler gpu_scheduler_{&sync_point_manager_};
FrameSinkManagerImpl manager_;
std::unique_ptr<CompositorFrameSinkSupport> support_;
ParentLocalSurfaceIdAllocator id_allocator_;
scoped_refptr<base::NullTaskRunner> task_runner_;
std::unique_ptr<BeginFrameSource> begin_frame_source_;
std::unique_ptr<StubDisplayClient> client_; // Must outlive `display_`.
std::unique_ptr<Display> display_;
raw_ptr<TestSoftwareOutputDevice> software_output_device_ = nullptr;
raw_ptr<FakeSoftwareOutputSurface> output_surface_ = nullptr;
raw_ptr<FakeSkiaOutputSurface> skia_output_surface_ = nullptr;
raw_ptr<TestDisplayScheduler> scheduler_ = nullptr;
};
// Check that frame is damaged and swapped only under correct conditions.
TEST_F(DisplayTest, DisplayDamaged) {
RendererSettings settings;
settings.partial_swap_enabled = true;
SetUpSoftwareDisplay(settings);
display_->Initialize(client_.get(), manager_.surface_manager());
gfx::ColorSpace color_space_1 = gfx::ColorSpace::CreateXYZD50();
gfx::ColorSpace color_space_2 = gfx::ColorSpace::CreateSRGBLinear();
gfx::DisplayColorSpaces color_spaces_1(color_space_1);
gfx::DisplayColorSpaces color_spaces_2(color_space_2);
display_->SetDisplayColorSpaces(color_spaces_1);
EXPECT_FALSE(scheduler_->damaged());
id_allocator_.GenerateId();
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
display_->Resize(gfx::Size(100, 100));
// First draw from surface should have full damage.
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
ResetDamageForTest();
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
EXPECT_TRUE(scheduler_->damaged());
EXPECT_FALSE(scheduler_->swapped());
EXPECT_EQ(0u, output_surface_->num_sent_frames());
EXPECT_EQ(gfx::ColorSpace(), output_surface_->last_reshape_color_space());
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
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 a small area is damaged but the color space changes which should
// result in full damage.
{
pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
ResetDamageForTest();
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
EXPECT_EQ(color_space_1, output_surface_->last_reshape_color_space());
display_->SetDisplayColorSpaces(color_spaces_2);
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
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(0, 0, 100, 100),
software_output_device_->damage_rect());
EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
}
// Same frame as above but no color space change. Only partial area should be
// drawn.
{
pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
ResetDamageForTest();
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
EXPECT_EQ(color_space_2, output_surface_->last_reshape_color_space());
display_->SetDisplayColorSpaces(color_spaces_2);
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_EQ(color_space_2, output_surface_->last_reshape_color_space());
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(3u, 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());
EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
}
// Pass has no damage so shouldn't be swapped.
{
pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 0, 0);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
ResetDamageForTest();
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(3u, output_surface_->num_sent_frames());
}
// Pass is wrong size so shouldn't be swapped. However, damage should
// result in latency info being stored for the next swap.
{
id_allocator_.GenerateId();
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
ResetDamageForTest();
constexpr gfx::Rect kOutputRect(0, 0, 99, 99);
constexpr gfx::Rect kDamageRect(10, 10, 10, 10);
CompositorFrame frame = CompositorFrameBuilder()
.AddRenderPass(kOutputRect, kDamageRect)
.AddLatencyInfo(ui::LatencyInfo())
.Build();
support_->SubmitCompositorFrame(id_allocator_.GetCurrentLocalSurfaceId(),
std::move(frame));
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(3u, output_surface_->num_sent_frames());
}
// Previous frame wasn't swapped, so next swap should have full damage.
{
pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 0, 0);
pass->id = CompositorRenderPassId{1u};
id_allocator_.GenerateId();
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
pass_list.push_back(std::move(pass));
ResetDamageForTest();
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(4u, output_surface_->num_sent_frames());
EXPECT_EQ(gfx::Rect(0, 0, 100, 100),
software_output_device_->damage_rect());
EXPECT_EQ(1u, output_surface_->last_sent_frame()->latency_info.size());
}
// Pass has copy output request so should be swapped.
{
pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 0, 0);
base::RunLoop copy_run_loop;
bool copy_called = false;
pass->copy_requests.push_back(std::make_unique<CopyOutputRequest>(
CopyOutputRequest::ResultFormat::RGBA,
CopyOutputRequest::ResultDestination::kSystemMemory,
base::BindOnce(&CopyCallback, &copy_called,
copy_run_loop.QuitClosure())));
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
ResetDamageForTest();
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(5u, output_surface_->num_sent_frames());
copy_run_loop.Run();
EXPECT_TRUE(copy_called);
}
// Pass has no damage, so shouldn't be swapped and latency info should be
// discarded.
{
ResetDamageForTest();
constexpr gfx::Rect kOutputRect(0, 0, 100, 100);
constexpr gfx::Rect kDamageRect(10, 10, 0, 0);
CompositorFrame frame = CompositorFrameBuilder()
.AddRenderPass(kOutputRect, kDamageRect)
.AddLatencyInfo(ui::LatencyInfo())
.Build();
frame.metadata.latency_info.push_back(ui::LatencyInfo());
support_->SubmitCompositorFrame(id_allocator_.GetCurrentLocalSurfaceId(),
std::move(frame));
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(5u, output_surface_->num_sent_frames());
}
// DisableSwapUntilResize() should cause a swap if no frame was swapped at the
// previous size.
{
id_allocator_.GenerateId();
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
scheduler_->reset_swapped_for_test();
display_->Resize(gfx::Size(200, 200));
EXPECT_FALSE(scheduler_->swapped());
EXPECT_EQ(5u, output_surface_->num_sent_frames());
ResetDamageForTest();
constexpr gfx::Rect kOutputRect(0, 0, 200, 200);
constexpr gfx::Rect kDamageRect(10, 10, 10, 10);
CompositorFrame frame = CompositorFrameBuilder()
.AddRenderPass(kOutputRect, kDamageRect)
.Build();
support_->SubmitCompositorFrame(id_allocator_.GetCurrentLocalSurfaceId(),
std::move(frame));
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
display_->DisableSwapUntilResize(base::OnceClosure());
display_->Resize(gfx::Size(100, 100));
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(6u, output_surface_->num_sent_frames());
EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
}
// Surface that's damaged completely should be resized and swapped.
{
id_allocator_.GenerateId();
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.0f);
pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 99, 99);
pass->damage_rect = gfx::Rect(0, 0, 99, 99);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
ResetDamageForTest();
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
EXPECT_TRUE(scheduler_->damaged());
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(7u, 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());
}
}
// Verifies latency info is stored only up to a limit if a swap fails.
void DisplayTest::LatencyInfoCapTest(bool over_capacity) {
SetUpSoftwareDisplay(RendererSettings());
display_->Initialize(client_.get(), manager_.surface_manager());
id_allocator_.GenerateId();
LocalSurfaceId local_surface_id(id_allocator_.GetCurrentLocalSurfaceId());
display_->SetLocalSurfaceId(local_surface_id, 1.f);
display_->Resize(gfx::Size(100, 100));
// Start off with a successful swap so output_surface_->last_sent_frame() is
// valid.
constexpr gfx::Rect kOutputRect(0, 0, 100, 100);
constexpr gfx::Rect kDamageRect(10, 10, 1, 1);
CompositorFrame frame1 =
CompositorFrameBuilder().AddRenderPass(kOutputRect, kDamageRect).Build();
support_->SubmitCompositorFrame(local_surface_id, std::move(frame1));
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_EQ(1u, output_surface_->num_sent_frames());
EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
// Resize so the swap fails even though there's damage, which triggers
// the case where we store latency info to append to a future swap.
display_->Resize(gfx::Size(200, 200));
// This is the same as LatencyInfo::kMaxLatencyInfoNumber.
const size_t max_latency_info_count = 100;
size_t latency_count = max_latency_info_count;
if (over_capacity)
latency_count++;
std::vector<ui::LatencyInfo> latency_info(latency_count, ui::LatencyInfo());
CompositorFrame frame2 = CompositorFrameBuilder()
.AddRenderPass(kOutputRect, kDamageRect)
.AddLatencyInfos(std::move(latency_info))
.Build();
support_->SubmitCompositorFrame(local_surface_id, std::move(frame2));
EXPECT_TRUE(
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()}));
EXPECT_EQ(1u, output_surface_->num_sent_frames());
EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
// Run a successful swap and verify whether or not LatencyInfo was discarded.
display_->Resize(gfx::Size(100, 100));
CompositorFrame frame3 =
CompositorFrameBuilder().AddRenderPass(kOutputRect, kDamageRect).Build();
support_->SubmitCompositorFrame(local_surface_id, std::move(frame3));
EXPECT_TRUE(
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()}));
// Verify whether or not LatencyInfo was dropped.
size_t expected_size = 0;
if (!over_capacity)
expected_size += max_latency_info_count;
EXPECT_EQ(2u, output_surface_->num_sent_frames());
EXPECT_EQ(expected_size,
output_surface_->last_sent_frame()->latency_info.size());
}
TEST_F(DisplayTest, UnderLatencyInfoCap) {
LatencyInfoCapTest(false);
}
TEST_F(DisplayTest, OverLatencyInfoCap) {
LatencyInfoCapTest(true);
}
TEST_F(DisplayTest, DisableSwapUntilResize) {
id_allocator_.GenerateId();
LocalSurfaceId local_surface_id1(id_allocator_.GetCurrentLocalSurfaceId());
id_allocator_.GenerateId();
LocalSurfaceId local_surface_id2(id_allocator_.GetCurrentLocalSurfaceId());
RendererSettings settings;
settings.partial_swap_enabled = true;
SetUpGpuDisplay(settings);
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(local_surface_id1, 1.f);
display_->Resize(gfx::Size(100, 100));
{
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
SubmitCompositorFrame(&pass_list, local_surface_id1);
}
EXPECT_FALSE(scheduler_->swapped());
// DisableSwapUntilResize() should trigger a swap because we have a frame of
// the correct size and haven't swapped at that size yet.
bool swap_callback_run = false;
display_->DisableSwapUntilResize(base::BindLambdaForTesting(
[&swap_callback_run]() { swap_callback_run = true; }));
EXPECT_TRUE(scheduler_->swapped());
gpu::SwapBuffersCompleteParams params;
params.swap_response.timings = GetTestSwapTimings();
params.swap_response.result = gfx::SwapResult::SWAP_ACK;
display_->DidReceiveSwapBuffersAck(params,
/*release_fence=*/gfx::GpuFenceHandle());
EXPECT_TRUE(swap_callback_run);
display_->Resize(gfx::Size(150, 150));
scheduler_->reset_swapped_for_test();
// DisableSwapUntilResize() won't trigger a swap because there is no frame
// of the correct size to draw.
display_->SetLocalSurfaceId(local_surface_id2, 1.f);
display_->DisableSwapUntilResize(base::OnceClosure());
EXPECT_FALSE(scheduler_->swapped());
display_->Resize(gfx::Size(200, 200));
{
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 200, 200);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
SubmitCompositorFrame(&pass_list, local_surface_id2);
}
// DrawAndSwap() should trigger a swap at current size.
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
scheduler_->reset_swapped_for_test();
// DisableSwapUntilResize() won't trigger another swap because we already
// swapped a frame at the current size.
display_->DisableSwapUntilResize(base::OnceClosure());
EXPECT_FALSE(scheduler_->swapped());
}
TEST_F(DisplayTest, BackdropFilterTest) {
RendererSettings settings;
settings.partial_swap_enabled = true;
id_allocator_.GenerateId();
const LocalSurfaceId local_surface_id(
id_allocator_.GetCurrentLocalSurfaceId());
// Set up first display.
SetUpSoftwareDisplay(settings);
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(local_surface_id, 1.f);
// Create frame sink for a sub surface.
const LocalSurfaceId sub_local_surface_id1(6,
base::UnguessableToken::Create());
const SurfaceId sub_surface_id1(kAnotherFrameSinkId, sub_local_surface_id1);
auto sub_support1 = std::make_unique<CompositorFrameSinkSupport>(
nullptr, &manager_, kAnotherFrameSinkId, /*is_root=*/false);
// Create frame sink for another sub surface.
const LocalSurfaceId sub_local_surface_id2(7,
base::UnguessableToken::Create());
const SurfaceId sub_surface_id2(kAnotherFrameSinkId2, sub_local_surface_id2);
auto sub_support2 = std::make_unique<CompositorFrameSinkSupport>(
nullptr, &manager_, kAnotherFrameSinkId2, /*is_root=*/false);
// Main surface M, damage D, sub-surface B with backdrop filter.
// +-----------+
// | +----+ M|
// | |B +-|-+ |
// | +--|-+ | |
// | | D| |
// | +---+ |
// +-----------+
const gfx::Size display_size(100, 100);
const gfx::Rect damage_rect(20, 20, 40, 40);
display_->Resize(display_size);
const gfx::Rect sub_surface_rect(5, 5, 25, 25);
const gfx::Rect no_damage;
CompositorRenderPassId::Generator render_pass_id_generator;
for (size_t frame_num = 1; frame_num <= 2; ++frame_num) {
bool first_frame = frame_num == 1;
ResetDamageForTest();
{
// Sub-surface with backdrop-filter.
CompositorRenderPassList pass_list;
auto bd_pass = CompositorRenderPass::Create();
cc::FilterOperations backdrop_filters;
backdrop_filters.Append(cc::FilterOperation::CreateBlurFilter(5.0));
bd_pass->SetAll(
render_pass_id_generator.GenerateNextId(), sub_surface_rect,
no_damage, gfx::Transform(), cc::FilterOperations(), backdrop_filters,
SkPath::Rect(gfx::RectToSkRect(sub_surface_rect)), SubtreeCaptureId(),
sub_surface_rect.size(), ViewTransitionElementResourceId(), false,
false, false, false, false);
pass_list.push_back(std::move(bd_pass));
CompositorFrame frame = CompositorFrameBuilder()
.SetRenderPassList(std::move(pass_list))
.Build();
sub_support1->SubmitCompositorFrame(sub_local_surface_id1,
std::move(frame));
}
{
// Sub-surface with damage.
CompositorRenderPassList pass_list;
auto other_pass = CompositorRenderPass::Create();
other_pass->output_rect = gfx::Rect(display_size);
other_pass->damage_rect = damage_rect;
other_pass->id = render_pass_id_generator.GenerateNextId();
pass_list.push_back(std::move(other_pass));
CompositorFrame frame = CompositorFrameBuilder()
.SetRenderPassList(std::move(pass_list))
.Build();
sub_support2->SubmitCompositorFrame(sub_local_surface_id2,
std::move(frame));
}
{
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(display_size);
pass->damage_rect = damage_rect;
pass->id = render_pass_id_generator.GenerateNextId();
// Embed sub surface 1, with backdrop filter.
auto* shared_quad_state1 = pass->CreateAndAppendSharedQuadState();
shared_quad_state1->SetAll(
gfx::Transform(), /*quad_layer_rect=*/sub_surface_rect,
/*visible_quad_layer_rect=*/sub_surface_rect,
/*mask_filter_info=*/gfx::MaskFilterInfo(),
/*clip=*/std::nullopt, /*contents_opaque=*/true,
/*opacity=*/1.0f, SkBlendMode::kSrcOver, /*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* quad1 = pass->quad_list.AllocateAndConstruct<SurfaceDrawQuad>();
quad1->SetNew(shared_quad_state1, /*rect=*/sub_surface_rect,
/*visible_rect=*/sub_surface_rect,
SurfaceRange(std::nullopt, sub_surface_id1),
SkColors::kBlack,
/*stretch_content_to_fill_bounds=*/false);
quad1->allow_merge = false;
// Embed sub surface 2, with damage.
auto* shared_quad_state2 = pass->CreateAndAppendSharedQuadState();
gfx::Rect rect1(display_size);
shared_quad_state2->SetAll(
gfx::Transform(), /*quad_layer_rect=*/rect1,
/*visible_quad_layer_rect=*/rect1,
/*mask_filter_info=*/gfx::MaskFilterInfo(),
/*clip_rect=*/std::nullopt,
/*are_contents_opaque=*/true, /*opacity=*/1.0f, SkBlendMode::kSrcOver,
/*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* quad2 = pass->quad_list.AllocateAndConstruct<SurfaceDrawQuad>();
quad2->SetNew(shared_quad_state2, /*rect=*/rect1,
/*visible_rect=*/rect1,
SurfaceRange(std::nullopt, sub_surface_id2),
SkColors::kBlack,
/*stretch_content_to_fill_bounds=*/false);
quad2->allow_merge = false;
pass_list.push_back(std::move(pass));
SubmitCompositorFrame(&pass_list, local_surface_id);
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(frame_num, output_surface_->num_sent_frames());
EXPECT_EQ(display_size, software_output_device_->viewport_pixel_size());
// The damage rect produced by surface_aggregator only includes the
// damaged surface rect, and is not expanded for the backdrop-filter
// surface.
auto expected_damage =
first_frame ? gfx::Rect(display_size) : gfx::Rect(20, 20, 40, 40);
EXPECT_EQ(expected_damage, software_output_device_->damage_rect());
// The scissor rect is expanded by direct_renderer to include the
// overlapping pixel-moving backdrop filter surface.
auto expected_scissor_rect =
first_frame ? gfx::Rect(display_size) : gfx::Rect(5, 5, 55, 55);
EXPECT_EQ(
expected_scissor_rect,
display_->renderer_for_testing()->GetLastRootScissorRectForTesting());
}
}
}
// 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;
id_allocator_.GenerateId();
LocalSurfaceId local_surface_id(id_allocator_.GetCurrentLocalSurfaceId());
// Set up first display.
SetUpSoftwareDisplay(settings);
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(local_surface_id, 1.f);
// Set up second frame sink + display.
auto support2 = std::make_unique<CompositorFrameSinkSupport>(
nullptr, &manager_, kAnotherFrameSinkId, true /* is_root */);
auto begin_frame_source2 = std::make_unique<StubBeginFrameSource>();
auto scheduler_for_display2 = std::make_unique<TestDisplayScheduler>(
begin_frame_source2.get(), task_runner_.get());
TestDisplayScheduler* scheduler2 = scheduler_for_display2.get();
StubDisplayClient client2; // Must outlive `display2`.
auto display2 = CreateDisplay(
settings, kAnotherFrameSinkId, std::move(scheduler_for_display2),
std::make_unique<FakeSoftwareOutputSurface>(
std::make_unique<TestSoftwareOutputDevice>()));
manager_.RegisterBeginFrameSource(begin_frame_source2.get(),
kAnotherFrameSinkId);
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));
ResetDamageForTest();
scheduler2->ResetDamageForTest();
EXPECT_FALSE(scheduler_->damaged());
EXPECT_FALSE(scheduler2->damaged());
// Submit a frame for display_ with full damage.
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{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());
}
TEST_F(DisplayTest, CompositorFrameWithPresentationToken) {
RendererSettings settings;
id_allocator_.GenerateId();
const LocalSurfaceId local_surface_id(
id_allocator_.GetCurrentLocalSurfaceId());
// Set up first display.
SetUpSoftwareDisplay(settings);
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(local_surface_id, 1.f);
// Create frame sink for a sub surface.
const LocalSurfaceId sub_local_surface_id(6,
base::UnguessableToken::Create());
const SurfaceId sub_surface_id(kAnotherFrameSinkId, sub_local_surface_id);
MockCompositorFrameSinkClient sub_client;
auto sub_support = std::make_unique<CompositorFrameSinkSupport>(
&sub_client, &manager_, kAnotherFrameSinkId, false /* is_root */);
const gfx::Size display_size(100, 100);
display_->Resize(display_size);
const gfx::Size sub_surface_size(32, 32);
uint32_t frame_token_1 = kInvalidFrameToken;
uint32_t frame_token_2 = kInvalidFrameToken;
{
CompositorFrame frame =
CompositorFrameBuilder()
.AddRenderPass(gfx::Rect(sub_surface_size), gfx::Rect())
.SetBeginFrameSourceId(kBeginFrameSourceId)
.Build();
EXPECT_CALL(sub_client, DidReceiveCompositorFrameAck(_));
frame_token_1 = frame.metadata.frame_token;
sub_support->SubmitCompositorFrame(sub_local_surface_id, std::move(frame));
}
{
// Submit a frame for display_ with full damage.
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(display_size);
pass->damage_rect = gfx::Rect(display_size);
pass->id = CompositorRenderPassId{1};
auto* shared_quad_state1 = pass->CreateAndAppendSharedQuadState();
gfx::Rect rect1(display_size);
shared_quad_state1->SetAll(gfx::Transform(), /*quad_layer_rect=*/rect1,
/*visible_quad_layer_rect=*/rect1,
/*mask_filter_info=*/gfx::MaskFilterInfo(),
/*clip=*/std::nullopt, /*contents_opaque=*/false,
/*opacity_f=*/0.5f, SkBlendMode::kSrcOver,
/*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* quad1 = pass->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
quad1->SetNew(shared_quad_state1, rect1 /* rect */,
rect1 /* visible_rect */, SkColors::kBlack,
false /* force_anti_aliasing_off */);
auto* shared_quad_state2 = pass->CreateAndAppendSharedQuadState();
gfx::Rect rect2(gfx::Point(20, 20), sub_surface_size);
shared_quad_state2->SetAll(gfx::Transform(), /*quad_layer_rect=*/rect2,
/*visible_quad_layer_rect=*/rect2,
/*mask_filter_info=*/gfx::MaskFilterInfo(),
/*clip=*/std::nullopt, /*contents_opaque=*/false,
/*opacity_f=*/1.f, SkBlendMode::kSrcOver,
/*sorting_context=*/0,
/*layer_id=*/0u, /*fast_rounded_corner=*/false);
auto* quad2 = pass->quad_list.AllocateAndConstruct<SurfaceDrawQuad>();
quad2->SetNew(shared_quad_state2, rect2 /* rect */,
rect2 /* visible_rect */,
SurfaceRange(std::nullopt, sub_surface_id), SkColors::kBlack,
false /* stretch_content_to_fill_bounds */);
pass_list.push_back(std::move(pass));
SubmitCompositorFrame(&pass_list, local_surface_id);
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
RunUntilIdle();
}
{
CompositorFrame frame = CompositorFrameBuilder()
.AddRenderPass(gfx::Rect(sub_surface_size),
gfx::Rect(sub_surface_size))
.SetBeginFrameSourceId(kBeginFrameSourceId)
.Build();
frame_token_2 = frame.metadata.frame_token;
EXPECT_CALL(sub_client, DidReceiveCompositorFrameAck(_));
sub_support->SubmitCompositorFrame(sub_local_surface_id, std::move(frame));
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
RunUntilIdle();
// Both frames with frame-tokens 1 and 2 requested presentation-feedback.
ASSERT_EQ(2u, sub_support->timing_details().size());
EXPECT_EQ(sub_support->timing_details().count(frame_token_1), 1u);
EXPECT_EQ(sub_support->timing_details().count(frame_token_2), 1u);
}
{
CompositorFrame frame =
CompositorFrameBuilder()
.AddRenderPass(gfx::Rect(sub_surface_size), gfx::Rect())
.SetBeginFrameSourceId(kBeginFrameSourceId)
.Build();
EXPECT_CALL(sub_client, DidReceiveCompositorFrameAck(_));
sub_support->SubmitCompositorFrame(sub_local_surface_id, std::move(frame));
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
RunUntilIdle();
}
}
TEST_F(DisplayTest, BeginFrameThrottling) {
base::test::ScopedFeatureList scoped_features;
scoped_features.InitAndDisableFeature(features::kNoCompositorFrameAcks);
id_allocator_.GenerateId();
SetUpGpuDisplay(RendererSettings());
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
support_->SetNeedsBeginFrame(true);
// Helper fn to submit a CF.
auto submit_frame = [this]() {
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
};
// Submit kUndrawnFrameLimit+1 frames. BeginFrames should be throttled only
// after the last frame.
base::TimeTicks frame_time;
for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
++i) {
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
submit_frame();
// Until we reach throttling we should return true.
if (i < CompositorFrameSinkSupport::kUndrawnFrameLimit) {
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
} else {
EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
}
// Clear the presentation feedbacks.
UpdateBeginFrameTime(support_.get(), frame_time);
}
frame_time = base::TimeTicks::Now();
EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
// Drawing should unthrottle begin-frames.
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
// Verify that throttling starts again after kUndrawnFrameLimit+1 frames.
for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
++i) {
// This clears the presentation feedbacks.
UpdateBeginFrameTime(support_.get(), frame_time);
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
submit_frame();
// Until we reach throttling we should return true.
if (i < CompositorFrameSinkSupport::kUndrawnFrameLimit) {
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
} else {
EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
}
// Clear the presentation feedbacks.
UpdateBeginFrameTime(support_.get(), frame_time);
}
frame_time = base::TimeTicks::Now();
EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
// Instead of doing a draw, forward time by ~1 seconds. That should unthrottle
// the begin-frame.
frame_time += base::Seconds(1.1);
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
}
TEST_F(DisplayTest, BeginFrameThrottlingMultipleSurfaces) {
base::test::ScopedFeatureList scoped_features;
scoped_features.InitAndDisableFeature(features::kNoCompositorFrameAcks);
id_allocator_.GenerateId();
SetUpGpuDisplay(RendererSettings());
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
support_->SetNeedsBeginFrame(true);
// Helper fn to submit a CF.
auto submit_frame = [this]() {
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
};
// Submit kUndrawnFrameLimit frames. BeginFrames should be throttled only
// after the last frame.
base::TimeTicks frame_time;
for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
++i) {
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
submit_frame();
// Generate a new LocalSurfaceId for the next submission.
id_allocator_.GenerateId();
}
frame_time = base::TimeTicks::Now();
EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
// This only draws the first surface, so we should only be able to send one
// more BeginFrame.
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
// After this frame submission, we are throttled again.
submit_frame();
frame_time = base::TimeTicks::Now();
EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
// Now the last surface is drawn. This should unblock us to submit
// kUndrawnFrameLimit+1 frames again.
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
id_allocator_.GenerateId();
for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
++i) {
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
submit_frame();
// Generate a new LocalSurfaceId for the next submission.
id_allocator_.GenerateId();
}
frame_time = base::TimeTicks::Now();
EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
UpdateBeginFrameTime(support_.get(), frame_time);
}
TEST_F(DisplayTest, DontThrottleWhenParentBlocked) {
id_allocator_.GenerateId();
SetUpGpuDisplay(RendererSettings());
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
support_->SetNeedsBeginFrame(true);
// Create frame sink for a sub surface.
const LocalSurfaceId sub_local_surface_id(6,
base::UnguessableToken::Create());
const LocalSurfaceId sub_local_surface_id2(7,
base::UnguessableToken::Create());
const SurfaceId sub_surface_id2(kAnotherFrameSinkId, sub_local_surface_id2);
MockCompositorFrameSinkClient sub_client;
auto sub_support = std::make_unique<CompositorFrameSinkSupport>(
&sub_client, &manager_, kAnotherFrameSinkId, false /* is_root */);
sub_support->SetNeedsBeginFrame(true);
// Submit kUndrawnFrameLimit+1 frames. BeginFrames should be throttled only
// after the last frame.
base::TimeTicks frame_time;
for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
++i) {
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
UpdateBeginFrameTime(sub_support.get(), frame_time);
sub_support->SubmitCompositorFrame(sub_local_surface_id,
MakeDefaultCompositorFrame());
// Until we reach throttling we should return true.
if (i < CompositorFrameSinkSupport::kUndrawnFrameLimit) {
EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
} else {
EXPECT_FALSE(ShouldSendBeginFrame(sub_support.get(), frame_time));
}
// Clear the presentation feedbacks.
UpdateBeginFrameTime(sub_support.get(), frame_time);
}
frame_time = base::TimeTicks::Now();
EXPECT_FALSE(ShouldSendBeginFrame(sub_support.get(), frame_time));
UpdateBeginFrameTime(sub_support.get(), frame_time);
// Make the display block on |sub_local_surface_id2|.
CompositorFrame frame =
CompositorFrameBuilder()
.AddDefaultRenderPass()
.SetActivationDependencies({sub_surface_id2})
.SetDeadline(FrameDeadline(base::TimeTicks::Now(),
std::numeric_limits<uint32_t>::max(),
base::Seconds(1), false))
.Build();
support_->SubmitCompositorFrame(id_allocator_.GetCurrentLocalSurfaceId(),
std::move(frame));
for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit * 3;
++i) {
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
UpdateBeginFrameTime(sub_support.get(), frame_time);
sub_support->SubmitCompositorFrame(sub_local_surface_id,
MakeDefaultCompositorFrame());
// Immediately after submitting frame, because there is presentation
// feedback queued up, ShouldSendBeginFrame should always return true.
EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
// Clear the presentation feedbacks.
UpdateBeginFrameTime(sub_support.get(), frame_time);
}
// Now submit to |sub_local_surface_id2|. This should unblock the parent and
// throttling will resume.
frame_time = base::TimeTicks::Now();
EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
UpdateBeginFrameTime(sub_support.get(), frame_time);
sub_support->SubmitCompositorFrame(sub_local_surface_id2,
MakeDefaultCompositorFrame());
frame_time = base::TimeTicks::Now();
EXPECT_FALSE(ShouldSendBeginFrame(sub_support.get(), frame_time));
UpdateBeginFrameTime(sub_support.get(), frame_time);
}
TEST_F(DisplayTest, DisplayTransformHint) {
SetUpSoftwareDisplay(RendererSettings());
display_->Initialize(client_.get(), manager_.surface_manager());
id_allocator_.GenerateId();
LocalSurfaceId local_surface_id(id_allocator_.GetCurrentLocalSurfaceId());
display_->SetLocalSurfaceId(local_surface_id, 1.f);
constexpr gfx::Size kSize = gfx::Size(100, 80);
constexpr gfx::Size kTransposedSize =
gfx::Size(kSize.height(), kSize.width());
display_->Resize(kSize);
const struct {
bool support_display_transform;
gfx::OverlayTransform display_transform_hint;
gfx::Size expected_size;
} kTestCases[] = {
// Output size is always the display size when output surface does not
// support display transform hint.
{false, gfx::OVERLAY_TRANSFORM_NONE, kSize},
{false, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90, kSize},
{false, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180, kSize},
{false, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270, kSize},
// Output size is transposed on 90/270 degree rotation when output surface
// supports display transform hint.
{true, gfx::OVERLAY_TRANSFORM_NONE, kSize},
{true, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90, kTransposedSize},
{true, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180, kSize},
{true, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270, kTransposedSize},
};
size_t expected_frame_sent = 0u;
for (const auto& test : kTestCases) {
SCOPED_TRACE(testing::Message()
<< "support_display_transform="
<< test.support_display_transform
<< ", display_transform_hint=" << test.display_transform_hint);
output_surface_->set_support_display_transform_hint(
test.support_display_transform);
constexpr gfx::Rect kOutputRect(gfx::Point(0, 0), kSize);
constexpr gfx::Rect kDamageRect(10, 10, 1, 1);
CompositorFrame frame = CompositorFrameBuilder()
.AddRenderPass(kOutputRect, kDamageRect)
.Build();
frame.metadata.display_transform_hint = test.display_transform_hint;
support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_EQ(++expected_frame_sent, output_surface_->num_sent_frames());
EXPECT_EQ(test.expected_size,
software_output_device_->viewport_pixel_size());
}
}
TEST_F(DisplayTest, DisplaySizeMismatch) {
RendererSettings settings;
settings.partial_swap_enabled = true;
settings.auto_resize_output_surface = false;
SetUpSoftwareDisplay(settings);
display_->Initialize(client_.get(), manager_.surface_manager());
id_allocator_.GenerateId();
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
display_->Resize(gfx::Size(100, 100));
// Pass has copy output request but wrong size so it should be drawn, but not
// swapped.
{
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 99, 99);
pass->damage_rect = gfx::Rect(10, 10, 0, 0);
base::RunLoop copy_run_loop;
bool copy_called = false;
pass->copy_requests.push_back(std::make_unique<CopyOutputRequest>(
CopyOutputRequest::ResultFormat::RGBA,
CopyOutputRequest::ResultDestination::kSystemMemory,
base::BindOnce(&CopyCallback, &copy_called,
copy_run_loop.QuitClosure())));
pass->id = CompositorRenderPassId{1u};
CompositorRenderPassList pass_list;
pass_list.push_back(std::move(pass));
SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
EXPECT_TRUE(scheduler_->damaged());
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
copy_run_loop.Run();
// Expect no swap happen
EXPECT_EQ(0u, output_surface_->num_sent_frames());
// Expect draw and copy output request happen
EXPECT_TRUE(copy_called);
// Expect there is no pending
EXPECT_EQ(pending_presentation_group_timings_size(), 0u);
}
}
TEST_F(DisplayTest, PixelMovingForegroundFilterTest) {
RendererSettings settings;
settings.partial_swap_enabled = true;
id_allocator_.GenerateId();
const LocalSurfaceId local_surface_id(
id_allocator_.GetCurrentLocalSurfaceId());
// Set up first display.
SetUpSoftwareDisplay(settings);
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(local_surface_id, 1.f);
// Create frame sink for a sub surface.
TestSurfaceIdAllocator sub_surface_id1(kAnotherFrameSinkId);
auto sub_support1 = std::make_unique<CompositorFrameSinkSupport>(
nullptr, &manager_, kAnotherFrameSinkId, /*is_root=*/false);
// Create frame sink for another sub surface.
TestSurfaceIdAllocator sub_surface_id2(kAnotherFrameSinkId2);
auto sub_support2 = std::make_unique<CompositorFrameSinkSupport>(
nullptr, &manager_, kAnotherFrameSinkId2, /*is_root=*/false);
// Main surface M, damage D, sub-surface B with foreground filter.
// +-----------+
// | +----+ M|
// | |B +-|-+ |
// | +--|-+ | |
// | | D| |
// | +---+ |
// +-----------+
const gfx::Size display_size(100, 100);
const gfx::Rect damage_rect(20, 20, 40, 40);
display_->Resize(display_size);
const gfx::Rect sub_surface_rect(5, 5, 25, 25);
const gfx::Rect no_damage;
CompositorRenderPassId::Generator render_pass_id_generator;
for (size_t frame_num = 1; frame_num <= 2; ++frame_num) {
bool first_frame = frame_num == 1;
ResetDamageForTest();
{
// Sub-surface with pixel-moving foreground filter - drop shadow filter
CompositorRenderPassList pass_list;
auto bd_pass = CompositorRenderPass::Create();
cc::FilterOperations foreground_filters;
foreground_filters.Append(cc::FilterOperation::CreateDropShadowFilter(
gfx::Point(5, 10), 2.f, SkColors::kTransparent));
bd_pass->SetAll(render_pass_id_generator.GenerateNextId(),
sub_surface_rect, no_damage, gfx::Transform(),
foreground_filters, cc::FilterOperations(),
SkPath::Rect(gfx::RectToSkRect(sub_surface_rect)),
SubtreeCaptureId(), sub_surface_rect.size(),
ViewTransitionElementResourceId(), false, false, false,
false, false);
pass_list.push_back(std::move(bd_pass));
CompositorFrame frame = CompositorFrameBuilder()
.SetRenderPassList(std::move(pass_list))
.Build();
sub_support1->SubmitCompositorFrame(sub_surface_id1.local_surface_id(),
std::move(frame));
}
{
// Sub-surface with damage.
CompositorRenderPassList pass_list;
auto other_pass = CompositorRenderPass::Create();
other_pass->output_rect = gfx::Rect(display_size);
other_pass->damage_rect = damage_rect;
other_pass->id = render_pass_id_generator.GenerateNextId();
pass_list.push_back(std::move(other_pass));
CompositorFrame frame = CompositorFrameBuilder()
.SetRenderPassList(std::move(pass_list))
.Build();
sub_support2->SubmitCompositorFrame(sub_surface_id2.local_surface_id(),
std::move(frame));
}
{
auto frame = CompositorFrameBuilder()
.AddRenderPass(
RenderPassBuilder(display_size)
.AddSurfaceQuad(
sub_surface_rect,
SurfaceRange(std::nullopt, sub_surface_id1),
{.allow_merge = false})
.AddSurfaceQuad(
gfx::Rect(display_size),
SurfaceRange(std::nullopt, sub_surface_id2),
{.allow_merge = false})
.SetDamageRect(damage_rect))
.Build();
support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
EXPECT_EQ(frame_num, output_surface_->num_sent_frames());
EXPECT_EQ(display_size, software_output_device_->viewport_pixel_size());
auto expected_damage =
first_frame ? gfx::Rect(display_size) : damage_rect;
EXPECT_EQ(expected_damage, software_output_device_->damage_rect());
// The scissor rect is expanded by direct_renderer to include the
// overlapping pixel-moving foreground filter surface.
auto expected_scissor_rect =
first_frame ? gfx::Rect(display_size) : gfx::Rect(4, 5, 56, 55);
EXPECT_EQ(
expected_scissor_rect,
display_->renderer_for_testing()->GetLastRootScissorRectForTesting());
}
}
}
TEST_F(DisplayTest, CanSkipRenderPass) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
features::kAllowUndamagedNonrootRenderPassToSkip);
id_allocator_.GenerateId();
const LocalSurfaceId local_surface_id(
id_allocator_.GetCurrentLocalSurfaceId());
// Set up first display.
SetUpSoftwareDisplay(RendererSettings());
display_->Initialize(client_.get(), manager_.surface_manager());
display_->SetLocalSurfaceId(local_surface_id, 1.f);
// Create frame sink for a sub surface.
TestSurfaceIdAllocator sub_surface_id1(kAnotherFrameSinkId);
auto sub_support1 = std::make_unique<CompositorFrameSinkSupport>(
nullptr, &manager_, kAnotherFrameSinkId, /*is_root=*/false);
// generate render pass id for the nonroot render pass.
CompositorRenderPassId::Generator render_pass_id_generator;
auto id_1 = render_pass_id_generator.GenerateNextId();
const gfx::Size display_size(100, 100);
const gfx::Rect root_damage_rect(20, 20, 40, 40);
display_->Resize(display_size);
const gfx::Rect sub_surface_rect(5, 5, 60, 60);
const gfx::Rect sub_surface_damage_rect(10, 10, 30, 30);
for (size_t frame_num = 1; frame_num <= 3; ++frame_num) {
ResetDamageForTest();
// Nonroot render pass with id_1. No update for frame #3.
if (frame_num != 3) {
CompositorRenderPassList pass_list;
auto bd_pass = CompositorRenderPass::Create();
bd_pass->output_rect = sub_surface_rect;
bd_pass->damage_rect = sub_surface_damage_rect;
bd_pass->has_damage_from_contributing_content = true;
bd_pass->id = id_1;
pass_list.push_back(std::move(bd_pass));
CompositorFrame frame = CompositorFrameBuilder()
.SetRenderPassList(std::move(pass_list))
.Build();
sub_support1->SubmitCompositorFrame(sub_surface_id1.local_surface_id(),
std::move(frame));
}
// Root render pass
{
auto frame =
CompositorFrameBuilder()
.AddRenderPass(RenderPassBuilder(display_size)
.AddSurfaceQuad(sub_surface_rect,
SurfaceRange(std::nullopt,
sub_surface_id1),
{.allow_merge = false})
.SetDamageRect(root_damage_rect))
.Build();
support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
scheduler_->reset_swapped_for_test();
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
EXPECT_TRUE(scheduler_->swapped());
// Number of skipped non-root render passes.
auto* skipped = display_->renderer_for_testing()
->GetLastSkippedRenderPassIdsForTesting();
if (frame_num != 3) {
// Whether the render pass can be skpped or not depends on the flag
// pass->has_damage_from_contributing_content and the render pass
// damage rect.
EXPECT_EQ(0u, skipped->size());
} else {
// No frame update for the sub surface. The nonroot render pass damage
// rect will be zero. pass->has_damage_from_contributing_content becomes
// false when there is no frame update. The associated non-render pass
// can be skipped.
EXPECT_EQ(1u, skipped->size());
}
}
}
}
class SkiaDelegatedInkRendererTest : public DisplayTest {
public:
~SkiaDelegatedInkRendererTest() override {
// Reset `client_` in `display_` to avoid accessing DisplayClient after
// `client_` is destructed. Without this, `display_` which is declared in
// DisplayTest class is destructed after `client_` which is declared in this
// class.
display_->ResetDisplayClientForTesting(&client_);
}
void SetUpRenderers() {
SetUpGpuDisplay(RendererSettings());
// Initialize the renderer and create an ink renderer.
display_->Initialize(&client_, manager_.surface_manager());
auto renderer = std::make_unique<DelegatedInkPointRendererSkiaForTest>();
ink_renderer_ = renderer.get();
display_->renderer_for_testing()->SetDelegatedInkPointRendererSkiaForTest(
std::move(renderer));
}
DelegatedInkPointRendererBase* ink_renderer() {
return display_->renderer_for_testing()
->GetDelegatedInkPointRenderer(/*create_if_necessary=*/
false);
}
int UniqueStoredPointerIds() {
return ink_renderer()->GetPointsMapForTest().size();
}
int StoredPointsForPointerId(int32_t pointer_id) {
return GetPointsForPointerId(pointer_id).size();
}
const std::map<base::TimeTicks, gfx::DelegatedInkPoint>&
GetPointsForPointerId(int32_t pointer_id) {
DCHECK(ink_renderer()->GetPointsMapForTest().find(pointer_id) !=
ink_renderer()->GetPointsMapForTest().end());
return ink_renderer()
->GetPointsMapForTest()
.find(pointer_id)
->second.GetPoints();
}
void CreateAndStoreDelegatedInkPoint(const gfx::PointF& point,
base::TimeTicks timestamp,
int32_t pointer_id) {
ink_points_[pointer_id].emplace_back(point, timestamp, pointer_id);
ink_renderer()->StoreDelegatedInkPoint(ink_points_[pointer_id].back());
}
void CreateAndStoreDelegatedInkPointFromPreviousPoint(int32_t pointer_id) {
DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
gfx::PointF point(ink_points_[pointer_id].back().point());
point.Offset(10, 10);
base::TimeTicks timestamp = ink_points_[pointer_id].back().timestamp();
timestamp += base::Milliseconds(5);
CreateAndStoreDelegatedInkPoint(point, timestamp, pointer_id);
}
void StoreAlreadyCreatedDelegatedInkPoints() {
DCHECK_EQ(static_cast<int>(ink_points_.size()), 1);
StoreAlreadyCreatedDelegatedInkPoints(ink_points_.begin()->first);
}
void StoreAlreadyCreatedDelegatedInkPoints(int32_t pointer_id) {
DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
for (gfx::DelegatedInkPoint ink_point : ink_points_[pointer_id])
ink_renderer()->StoreDelegatedInkPoint(ink_point);
}
void SendMetadata(gfx::DelegatedInkMetadata metadata) {
ink_renderer()->SetDelegatedInkMetadata(
std::make_unique<gfx::DelegatedInkMetadata>(metadata));
}
gfx::DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint(
int index,
float diameter,
SkColor4f color,
const gfx::RectF& presentation_area) {
DCHECK_EQ(static_cast<int>(ink_points_.size()), 1);
return MakeAndSendMetadataFromStoredInkPoint(
ink_points_.begin()->first, index, diameter, color, presentation_area);
}
gfx::DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint(
int32_t pointer_id,
int index,
float diameter,
SkColor4f color,
const gfx::RectF& presentation_area) {
DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
EXPECT_GE(index, 0);
EXPECT_LT(index, ink_points_size(pointer_id));
// TODO(crbug.com/40219248): gfx::DelegatedInkMetadata to SkColor4f
gfx::DelegatedInkMetadata metadata(
ink_points_[pointer_id][index].point(), diameter, color.toSkColor(),
ink_points_[pointer_id][index].timestamp(), presentation_area,
base::TimeTicks::Now(),
/*hovering*/ false, /*render_pass_id=*/0);
SendMetadata(metadata);
return metadata;
}
void HistogramCheck(const base::HistogramTester& histograms,
base::TimeDelta expected_bucket,
const char* histogram_name) {
if (expected_bucket == base::TimeDelta::Min()) {
histograms.ExpectTotalCount(histogram_name, 0);
} else {
histograms.ExpectTotalCount(histogram_name, 1);
histograms.ExpectTimeBucketCount(histogram_name, expected_bucket, 1);
}
}
// Either bucket containing base::TimeDelta::Min() is interpreted to mean that
// expected total count of the histogram should be 0.
void FinalizePathAndCheckHistograms(
base::TimeDelta expected_bucket_without_prediction,
base::TimeDelta expected_bucket_with_prediction) {
base::HistogramTester histograms;
ink_renderer()->FinalizePathForDraw();
HistogramCheck(
histograms, expected_bucket_without_prediction,
"Renderer.DelegatedInkTrail.LatencyImprovement.Skia.WithoutPrediction");
HistogramCheck(
histograms, expected_bucket_with_prediction,
"Renderer.DelegatedInkTrail.LatencyImprovement.Skia.WithPrediction");
}
void DrawDelegatedInkTrail() {
SkCanvas canvas;
static_cast<DelegatedInkPointRendererSkia*>(ink_renderer())
->DrawDelegatedInkTrail(&canvas, gfx::Transform());
}
int GetPathPointCount() { return ink_renderer()->GetPathPointCountForTest(); }
// Explicitly get the metadata that is stored on the renderer.
const gfx::DelegatedInkMetadata* GetMetadataFromRenderer() {
return ink_renderer()->GetMetadataForTest();
}
const gfx::DelegatedInkPoint& ink_point(int index) {
DCHECK_EQ(static_cast<int>(ink_points_.size()), 1);
return ink_point(ink_points_.begin()->first, index);
}
const gfx::DelegatedInkPoint& ink_point(int32_t pointer_id, int index) {
DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
EXPECT_GE(index, 0);
EXPECT_LT(index, ink_points_size(pointer_id));
return ink_points_[pointer_id][index];
}
const gfx::DelegatedInkPoint& last_ink_point(int32_t pointer_id) {
DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
return ink_points_[pointer_id].back();
}
int ink_points_size() {
DCHECK_EQ(static_cast<int>(ink_points_.size()), 1);
return ink_points_.begin()->second.size();
}
int ink_points_size(int32_t pointer_id) {
DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
return ink_points_[pointer_id].size();
}
int points_to_predict() const { return kPointsToPredict; }
const base::TimeDelta time_into_the_future() const {
return base::Milliseconds(
(kMillisecondsIntoFuturePerPoint - kResampleLatency) *
kPointsToPredict);
}
protected:
raw_ptr<DelegatedInkPointRendererSkiaForTest> ink_renderer_ = nullptr;
// Stub client kept in scope to prevent access violations during DrawAndSwap.
StubDisplayClient client_;
base::test::ScopedFeatureList feature_list_;
private:
std::unordered_map<int32_t, std::vector<gfx::DelegatedInkPoint>> ink_points_;
// Values used to configure the points predictor. Needs to match the values
// in `DelegatedInkTrailData`;
static const int kPointsToPredict = 2;
static const int kMillisecondsIntoFuturePerPoint = 6;
static const int kResampleLatency = 5;
};
// Testing filtering points in the the delegated ink renderer when the skia
// renderer is in use.
TEST_F(SkiaDelegatedInkRendererTest, SkiaDelegatedInkRendererFilteringPoints) {
SetUpRenderers();
// First, a sanity check.
EXPECT_EQ(0, UniqueStoredPointerIds());
// Insert 3 arbitrary points into the ink renderer to confirm that they go
// where we expect and are all stored correctly.
const int kInitialDelegatedPoints = 3;
base::TimeTicks timestamp = base::TimeTicks::Now();
gfx::PointF point(10, 10);
const int32_t kPointerId = std::numeric_limits<int32_t>::max();
CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
for (int i = 1; i < kInitialDelegatedPoints; ++i)
CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerId);
// They all have the same pointer ID, so there should be exactly one unique
// element in the map, and that element should itself have all three points.
EXPECT_EQ(1, UniqueStoredPointerIds());
EXPECT_EQ(kInitialDelegatedPoints, StoredPointsForPointerId(kPointerId));
// Now provide metadata with a timestamp matching one of the points to
// confirm that earlier points are removed and later points remain.
const int kInkPointForMetadata = 1;
const float kDiameter = 1.f;
gfx::DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint(
kInkPointForMetadata, kDiameter, SkColors::kBlack, gfx::RectF());
// The histogram should count one in the bucket that is the difference between
// the latest point stored and the metadata. The *WithoutPrediction histogram
// should count the difference between the last point and the metadata, while
// the *WithPrediction* histogram should count 1 in the 7ms bucket because
// prediction can occer with linear resampling and 2 input points.
base::TimeDelta bucket_without_prediction =
last_ink_point(kPointerId).timestamp() - metadata.timestamp();
FinalizePathAndCheckHistograms(bucket_without_prediction,
base::Milliseconds(7));
EXPECT_EQ(kInitialDelegatedPoints - kInkPointForMetadata,
StoredPointsForPointerId(kPointerId));
EXPECT_EQ(metadata.point(),
GetPointsForPointerId(kPointerId).begin()->second.point());
EXPECT_EQ(last_ink_point(kPointerId).point(),
GetPointsForPointerId(kPointerId).rbegin()->second.point());
EXPECT_EQ(ink_point(0).pointer_id(), kPointerId);
// Confirm that the metadata is cleared when DrawDelegatedInkTrail() is
// called.
DrawDelegatedInkTrail();
EXPECT_FALSE(GetMetadataFromRenderer());
// Add more points than the maximum that will be stored to confirm only the
// max is stored and the correct ones are removed first.
const int kPointsBeyondMaxAllowed = 2;
StoreAlreadyCreatedDelegatedInkPoints();
while (ink_points_size() <
gfx::kMaximumNumberOfDelegatedInkPoints + kPointsBeyondMaxAllowed)
CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerId);
EXPECT_EQ(gfx::kMaximumNumberOfDelegatedInkPoints,
StoredPointsForPointerId(kPointerId));
EXPECT_EQ(ink_point(kPointsBeyondMaxAllowed).point(),
GetPointsForPointerId(kPointerId).begin()->second.point());
EXPECT_EQ(last_ink_point(kPointerId).point(),
GetPointsForPointerId(kPointerId).rbegin()->second.point());
EXPECT_EQ(last_ink_point(kPointerId).pointer_id(), kPointerId);
// Now send metadata with a timestamp before all of the points that are
// currently stored to confirm that no points are filtered out and the number
// stored remains the same. The *WithoutPrediction histogram should record 0
// improvement, but the *WithPrediction* one should not record anything at all
// due to not finding a matching pointer ID to predict with.
const int kExpectedPoints = StoredPointsForPointerId(kPointerId);
SendMetadata(metadata);
FinalizePathAndCheckHistograms(base::Milliseconds(0), base::TimeDelta::Min());
EXPECT_EQ(kExpectedPoints, StoredPointsForPointerId(kPointerId));
}
// Test filtering when points arrive with several different pointer IDs.
TEST_F(SkiaDelegatedInkRendererTest,
SkiaDelegatedInkRendererFilteringPointsWithMultiplePointerIds) {
SetUpRenderers();
// Unique pointer IDs used - numbers arbitrary.
const std::vector<int32_t> kPointerIds = {1, 20, 300};
// First add just one DelegatedInkPoint for each pointer id to confirm that
// they all get stored separately.
base::TimeTicks timestamp = base::TimeTicks::Now();
for (uint64_t i = 0; i < kPointerIds.size(); ++i) {
// Make sure that each pointer id has slightly different points so that when
// new points are added later that are based on previous points, it doesn't
// result in multiple pointer ids having identical DelegatedInkPoints
CreateAndStoreDelegatedInkPoint(gfx::PointF(i * 5, i * 10), timestamp,
kPointerIds[i]);
timestamp += base::Milliseconds(5);
}
EXPECT_EQ(static_cast<int>(kPointerIds.size()), UniqueStoredPointerIds());
for (int32_t pointer_id : kPointerIds)
EXPECT_EQ(1, StoredPointsForPointerId(pointer_id));
// Add more points so that the first pointer ID contains 4 DelegatedInkPoints,
// and the third pointer id contains 2 DelegatedInkPoints
const int kNumPointsForPointerId0 = 4;
while (ink_points_size(kPointerIds[0]) < kNumPointsForPointerId0)
CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerIds[0]);
CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerIds[2]);
// Confirm all the points got stored where they should have been.
for (int32_t pointer_id : kPointerIds) {
EXPECT_EQ(ink_points_size(pointer_id),
StoredPointsForPointerId(pointer_id));
}
// Now provide metadata with a timestamp matching one of the points in the
// first pointer id bucket to confirm that earlier points are removed and
// later points remain.
const int kInkPointForMetadata = 1;
const float kDiameter = 1.f;
gfx::DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint(
kPointerIds[0], kInkPointForMetadata, kDiameter, SkColors::kBlack,
gfx::RectF());
// 3 points should be enough for prediction to work, so the histogram should
// have one in the *WithoutPrediction bucket that matches the difference
// between the metadata and the final point, and one in the *WithPrediction
// bucket that matches the amount of prediction that is being done (plus the
// difference between the final point and the metadata).
base::TimeDelta bucket_without_prediction =
last_ink_point(kPointerIds[0]).timestamp() - metadata.timestamp();
FinalizePathAndCheckHistograms(
bucket_without_prediction,
bucket_without_prediction + time_into_the_future());
// Confirm the size, first, and last points of the first pointer ID are what
// we expect.
EXPECT_EQ(kNumPointsForPointerId0 - kInkPointForMetadata,
StoredPointsForPointerId(kPointerIds[0]));
EXPECT_EQ(metadata.point(),
GetPointsForPointerId(kPointerIds[0]).begin()->second.point());
EXPECT_EQ(last_ink_point(kPointerIds[0]).point(),
GetPointsForPointerId(kPointerIds[0]).rbegin()->second.point());
// Confirm that neither of the other pointer ids were impacted.
for (uint64_t i = 1; i < kPointerIds.size(); ++i) {
EXPECT_EQ(ink_points_size(kPointerIds[i]),
StoredPointsForPointerId(kPointerIds[i]));
}
// Send a metadata whose point and timestamp doesn't match any stored
// DelegatedInkPoint and confirm that it doesn't cause any changes to the
// stored values. *WithoutPrediction histogram should record 0 improvement,
// *WithPrediction* shouldn't record anything due to no valid pointer id.
SendMetadata(gfx::DelegatedInkMetadata(
gfx::PointF(100, 100), 5.6f, SK_ColorBLACK, base::TimeTicks::Min(),
gfx::RectF(), base::TimeTicks::Min(), /*hovering*/ false,
/*render_pass_id=*/0));
FinalizePathAndCheckHistograms(base::Milliseconds(0), base::TimeDelta::Min());
EXPECT_EQ(kNumPointsForPointerId0 - kInkPointForMetadata,
StoredPointsForPointerId(kPointerIds[0]));
for (uint64_t i = 1; i < kPointerIds.size(); ++i) {
EXPECT_EQ(ink_points_size(kPointerIds[i]),
StoredPointsForPointerId(kPointerIds[i]));
}
// Finally, send a metadata with a timestamp beyond all of the stored points.
// This should result in all of the points being erased, but the pointer ids
// will still exist as they contains the predictors as well.
SendMetadata(gfx::DelegatedInkMetadata(
gfx::PointF(100, 100), 5.6f, SK_ColorBLACK,
base::TimeTicks::Now() + base::Milliseconds(1000), gfx::RectF(),
base::TimeTicks::Now(), /*hovering*/ false, /*render_pass_id=*/0));
FinalizePathAndCheckHistograms(base::Milliseconds(0), base::TimeDelta::Min());
for (int i : kPointerIds)
EXPECT_EQ(0, StoredPointsForPointerId(i));
}
// Confirm that the delegated ink trail histograms record latency correctly.
TEST_F(SkiaDelegatedInkRendererTest, LatencyHistograms) {
SetUpRenderers();
// Confirm that nothing is counted in the histograms when there is no metadata
// or points to draw.
FinalizePathAndCheckHistograms(base::TimeDelta::Min(),
base::TimeDelta::Min());
// Insert 4 arbitrary points into the ink renderer to later draw.
base::TimeTicks timestamp = base::TimeTicks::Now();
const int32_t kPointerId = 17;
CreateAndStoreDelegatedInkPoint(gfx::PointF(20, 19), timestamp, kPointerId);
CreateAndStoreDelegatedInkPoint(
gfx::PointF(15, 19), timestamp + base::Milliseconds(8), kPointerId);
CreateAndStoreDelegatedInkPoint(
gfx::PointF(16, 28), timestamp + base::Milliseconds(16), kPointerId);
CreateAndStoreDelegatedInkPoint(
gfx::PointF(29, 35), timestamp + base::Milliseconds(24), kPointerId);
// Provide a metadata so that points can be drawn, based on the first ink
// point that was sent.
const float kDiameter = 11.99f;
MakeAndSendMetadataFromStoredInkPoint(/*index*/ 0, kDiameter,
SkColors::kBlack, gfx::RectF());
// *WithoutPrediction histogram should have one counted in the 24 ms bucket
// because that's the difference between the latest point and the metadata.
// *WithPrediction should be able to predict here, so it should contain 1 in
// the bucket that is |kNumberOfMillisecondsIntoFutureToPredictPerPoint| *
// |kNumberOfPointsToPredict| into the future from 24 ms bucket.
base::TimeDelta bucket_without_prediction = base::Milliseconds(24);
FinalizePathAndCheckHistograms(
bucket_without_prediction,
bucket_without_prediction + time_into_the_future());
// Now provide metadata that matches the final ink point provided, so that
// everything earlier is filtered out. Then the *WithoutPrediction histogram
// will count 1 in the 0 ms bucket and the *WithPrediction histogram will
// still be able to predict points, so it should have counted one.
MakeAndSendMetadataFromStoredInkPoint(/*index*/ 3, kDiameter,
SkColors::kBlack, gfx::RectF());
bucket_without_prediction = base::Milliseconds(0);
FinalizePathAndCheckHistograms(bucket_without_prediction,
time_into_the_future());
// DrawDelegatedInkTrail should clear the metadata, so finalizing the path
// shouldn't record anything in the histograms.
DrawDelegatedInkTrail();
FinalizePathAndCheckHistograms(base::TimeDelta::Min(),
base::TimeDelta::Min());
// Send a few more points but no metadata to confirm that nothing is counted.
timestamp = base::TimeTicks::Now();
CreateAndStoreDelegatedInkPoint(gfx::PointF(85, 56), timestamp, kPointerId);
CreateAndStoreDelegatedInkPoint(
gfx::PointF(96, 70), timestamp + base::Milliseconds(2), kPointerId);
CreateAndStoreDelegatedInkPoint(
gfx::PointF(112, 94), timestamp + base::Milliseconds(10), kPointerId);
FinalizePathAndCheckHistograms(base::TimeDelta::Min(),
base::TimeDelta::Min());
}
// Confirm that a delegated ink trail will still be drawn if the point and
// metadata are close enough.
TEST_F(SkiaDelegatedInkRendererTest, DrawTrailWhenMetadataIsCloseEnough) {
SetUpRenderers();
// Insert 3 points, then create a metadata that is not exactly the same as
// the first point, but within DelegatedInkPointRendererBase::kEpsilon of
// the point so that a trail is drawn.
base::TimeTicks timestamp = base::TimeTicks::Now();
base::TimeTicks timestamp2 = timestamp + base::Milliseconds(8);
gfx::PointF point(45.f, 78.f);
gfx::PointF point2(68.f, 89.f);
const int32_t kPointerId = 17;
CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
CreateAndStoreDelegatedInkPoint(point2, timestamp2, kPointerId);
CreateAndStoreDelegatedInkPoint(
gfx::PointF(80.f, 70.f), timestamp2 + base::Milliseconds(8), kPointerId);
gfx::DelegatedInkMetadata metadata(
gfx::PointF(point.x() - 1.0f, point.y() - 1.0f), 45.f, SK_ColorBLACK,
timestamp, gfx::RectF(0, 0, 100, 100), base::TimeTicks::Now(),
/*hovering*/ false, /*render_pass_id=*/0);
SendMetadata(metadata);
// If the metadata was close enough, then a trail should be drawn with all
// three points.
ink_renderer()->FinalizePathForDraw();
EXPECT_EQ(GetPathPointCount(), 3 + points_to_predict());
// Now send a metadata with a point that is slightly further away from the
// second point, such that the distance between them is greater than the
// kEpsilon value to confirm that if it gets too far away we won't use it for
// drawing.
metadata = gfx::DelegatedInkMetadata(
gfx::PointF(point2.x() - 1.01f, point2.y() - 1.0f), 45.f, SK_ColorBLACK,
timestamp2, gfx::RectF(0, 0, 100, 100), base::TimeTicks::Now(),
/*hovering*/ false, /*render_pass_id=*/0);
SendMetadata(metadata);
ink_renderer()->FinalizePathForDraw();
EXPECT_EQ(GetPathPointCount(), 0);
}
// Tests that the OutstandingPointsToDraw histogram is fired correctly.
TEST_F(SkiaDelegatedInkRendererTest, SkiaDelegatedInkOutstandingPointsToDraw) {
const std::string kHistogramName =
"Renderer.DelegatedInkTrail.Skia.OutstandingPointsToDraw";
const base::HistogramTester histogram_tester;
const int32_t kPointerId = 17;
SetUpRenderers();
ink_renderer()->ReportPointsDrawn();
// No histogram should be fired when there are no points to draw.
histogram_tester.ExpectTotalCount(kHistogramName, 0);
// Add one point, a histogram with a count of one should be fired.
const base::TimeTicks timestamp = base::TimeTicks::Now();
const gfx::PointF point(45.f, 78.f);
CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
SendMetadata(gfx::DelegatedInkMetadata(
gfx::PointF(point.x(), point.y()), 45.f, SK_ColorBLACK, timestamp,
gfx::RectF(0, 0, 100, 100), base::TimeTicks::Now(),
/*hovering=*/false, /*render_pass_id=*/0));
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectUniqueSample(kHistogramName, 1, 1);
// Add two point, a histogram with a count of two and three should be fired.
CreateAndStoreDelegatedInkPoint(point + gfx::Vector2d(1, 1),
timestamp + base::Milliseconds(10),
kPointerId);
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectTotalCount(kHistogramName, 2);
histogram_tester.ExpectBucketCount(kHistogramName, 2, 1);
CreateAndStoreDelegatedInkPoint(point + gfx::Vector2d(2, 2),
timestamp + base::Milliseconds(20),
kPointerId);
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectBucketCount(kHistogramName, 3, 1);
histogram_tester.ExpectTotalCount(kHistogramName, 3);
}
// Tests that the TimeToDrawMillis histogram is fired correctly.
TEST_F(SkiaDelegatedInkRendererTest, SkiaDelegatedInkTimeToDrawMillis) {
const std::string kHistogramName =
"Renderer.DelegatedInkTrail.Skia.TimeToDrawPointsMillis";
const base::HistogramTester histogram_tester;
constexpr int32_t kPointerId = 1u;
SetUpRenderers();
ink_renderer()->ReportPointsDrawn();
// No histogram should be fired when there are no points to draw.
histogram_tester.ExpectTotalCount(kHistogramName, 0);
// Add one point to the trail and ensure one histogram instance is fired.
const base::TimeTicks timestamp = base::TimeTicks::Now();
const gfx::PointF point(45.f, 78.f);
CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
SendMetadata(gfx::DelegatedInkMetadata(
gfx::PointF(point.x(), point.y()), 45.f, SK_ColorBLACK, timestamp,
gfx::RectF(0, 0, 100, 100), base::TimeTicks::Now(),
/*hovering=*/false, /*render_pass_id=*/0));
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectTotalCount(kHistogramName, 1);
// Add two points to the trail and ensure that the histogram is fired three
// times (three points, four total histogram fires accounting for the
// previous).
CreateAndStoreDelegatedInkPoint(point + gfx::Vector2d(1, 1),
timestamp + base::Milliseconds(1),
kPointerId);
CreateAndStoreDelegatedInkPoint(point + gfx::Vector2d(2, 2),
timestamp + base::Milliseconds(2),
kPointerId);
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectTotalCount(kHistogramName, 4);
}
TEST_F(SkiaDelegatedInkRendererTest,
SkiaDelegatedInkTimeFromDelegatedInkToApiPaint) {
const std::string kHistogramName =
"Renderer.DelegatedInkTrail.Skia.TimeFromDelegatedInkToApiPaint";
const base::HistogramTester histogram_tester;
constexpr int32_t kPointerId = 1u;
const auto create_metadata = [](gfx::PointF& p, base::TimeTicks& t) {
return gfx::DelegatedInkMetadata(p, /*diameter=*/45.f, SK_ColorBLACK, t,
gfx::RectF(0, 0, 100, 100), t,
/*hovering=*/false, /*render_pass_id=*/0);
};
SetUpRenderers();
ink_renderer()->ReportPointsDrawn();
// No histogram should be fired when `metadata_paint_time_` is not set.
histogram_tester.ExpectTotalCount(kHistogramName, 0);
// Original timestamp and coordinates to be advanced for subsequent points
// sent.
base::TimeTicks timestamp = base::TimeTicks::Now();
gfx::PointF point(45.f, 78.f);
const auto advance_point = [&]() {
timestamp += base::Milliseconds(10);
point += gfx::Vector2d(3.f, 3.f);
};
// Set up a trail, create a point and a metadata and call ReportPointsDrawn.
CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
SendMetadata(create_metadata(point, timestamp));
ink_renderer()->ReportPointsDrawn();
EXPECT_NE(std::nullopt, GetPointsForPointerId(kPointerId)
.find(timestamp)
->second.paint_timestamp());
ink_renderer()->ReportPointsDrawn();
// Add two delegated ink points to the trail and paint them.
advance_point();
CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
advance_point();
CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
ink_renderer()->ReportPointsDrawn();
// After drawing the points, all of them should have a `paint_timestamp_` set.
for (auto& [_, p] : GetPointsForPointerId(kPointerId)) {
EXPECT_NE(std::nullopt, p.paint_timestamp());
}
// Send a metadata that matches the last painted point.
SendMetadata(create_metadata(point, timestamp));
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectTotalCount(kHistogramName, 1);
// The histogram should not be fired when the metadata has not been updated.
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectTotalCount(kHistogramName, 1);
// Send same metadata as before and report drawing. The histogram should not
// be fired.
SendMetadata(create_metadata(point, timestamp));
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectTotalCount(kHistogramName, 1);
// Send a new point, draw, a new metadata that matches the new point, draw
// again and ensure that a new histogram is fired.
advance_point();
CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
ink_renderer()->ReportPointsDrawn();
SendMetadata(create_metadata(point, timestamp));
ink_renderer()->ReportPointsDrawn();
histogram_tester.ExpectTotalCount(kHistogramName, 2);
}
enum class DelegatedInkType { kPlatformInk, kSkiaInk };
class DelegatedInkDisplayTest
: public SkiaDelegatedInkRendererTest,
public testing::WithParamInterface<DelegatedInkType> {
public:
void SetUpGpuDisplaySkiaWithPlatformInk(const RendererSettings& settings) {
scoped_refptr<TestContextProvider> provider = TestContextProvider::Create();
provider->BindToCurrentSequence();
std::unique_ptr<FakeSkiaOutputSurface> skia_output_surface =
FakeSkiaOutputSurface::Create3d(std::move(provider));
// Set the delegated ink capability on the output surface to true so that
// path can be tested in Display::DrawAndSwap
skia_output_surface->UsePlatformDelegatedInkForTesting();
skia_output_surface_ = skia_output_surface.get();
CreateDisplaySchedulerAndDisplay(settings, kArbitraryFrameSinkId,
std::move(skia_output_surface));
}
void SetUpGpuDisplay() {
if (GetParam() == DelegatedInkType::kSkiaInk) {
SetUpRenderers();
} else {
// Set up the display to use the Skia renderer.
SetUpGpuDisplaySkiaWithPlatformInk(RendererSettings());
display_->Initialize(&client_, manager_.surface_manager());
}
}
void SubmitCompositorFrameWithInkMetadata(
CompositorRenderPassList* pass_list,
const LocalSurfaceId& local_surface_id,
const gfx::DelegatedInkMetadata& metadata) {
CompositorFrame frame = CompositorFrameBuilder()
.SetRenderPassList(std::move(*pass_list))
.AddDelegatedInkMetadata(metadata)
.Build();
pass_list->clear();
support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
}
const gfx::DelegatedInkMetadata* GetMetadataFromTestRenderer() {
return ink_renderer_->last_metadata();
}
};
struct DelegatedInkDisplayTestPassToString {
std::string operator()(
const testing::TestParamInfo<DelegatedInkType> type) const {
return type.param == DelegatedInkType::kPlatformInk ? "PlatformInk"
: "SkiaInk";
}
};
INSTANTIATE_TEST_SUITE_P(DelegatedInkTrails,
DelegatedInkDisplayTest,
testing::Values(DelegatedInkType::kPlatformInk,
DelegatedInkType::kSkiaInk),
DelegatedInkDisplayTestPassToString());
// Confirm that delegated ink metadata is not ever sent to both the delegated
// ink renderer and the output surface (for platform delegated ink), only one
// or the other.
TEST_P(DelegatedInkDisplayTest, MetadataOnlySentToSkiaRendererOrOutputSurface) {
SetUpGpuDisplay();
id_allocator_.GenerateId();
display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
display_->Resize(gfx::Size(100, 100));
CompositorRenderPassList pass_list;
auto pass = CompositorRenderPass::Create();
pass->output_rect = gfx::Rect(0, 0, 100, 100);
pass->damage_rect = gfx::Rect(10, 10, 1, 1);
pass->id = CompositorRenderPassId{1u};
pass_list.push_back(std::move(pass));
gfx::DelegatedInkMetadata metadata(
gfx::PointF(5, 5), 3.5f, SK_ColorBLACK, base::TimeTicks::Now(),
gfx::RectF(0, 0, 20, 20), base::TimeTicks::Now(), false,
/*render_pass_id=*/0);
SubmitCompositorFrameWithInkMetadata(
&pass_list, id_allocator_.GetCurrentLocalSurfaceId(), metadata);
display_->DrawAndSwap({base::TimeTicks::Now(), base::TimeTicks::Now()});
// Confirm that the metadata correctly made it to either the skia output
// surface, or the delegated ink renderer.
const gfx::DelegatedInkMetadata* retrieved_metadata =
GetParam() == DelegatedInkType::kPlatformInk
? skia_output_surface_->last_delegated_ink_metadata()
: GetMetadataFromTestRenderer();
EXPECT_TRUE(retrieved_metadata);
EXPECT_EQ(retrieved_metadata->point(), metadata.point());
EXPECT_EQ(retrieved_metadata->diameter(), metadata.diameter());
EXPECT_EQ(retrieved_metadata->color(), metadata.color());
EXPECT_EQ(retrieved_metadata->timestamp(), metadata.timestamp());
EXPECT_EQ(retrieved_metadata->presentation_area(),
metadata.presentation_area());
EXPECT_EQ(retrieved_metadata->is_hovering(), metadata.is_hovering());
// Confirm that metadata wasn't sent to the SkiaOutputSurface if Skia was
// used for drawing, or confirm that the DelegatedInkPointRenderer wasn't
// created if platform ink is being used.
if (GetParam() == DelegatedInkType::kPlatformInk)
EXPECT_FALSE(ink_renderer());
else
EXPECT_FALSE(skia_output_surface_->last_delegated_ink_metadata());
}
// Check that a pending delegated ink point renderer sent to the display
// correctly goes to either the renderer or the output surface depending on if
// the platform supports delegated ink and the feature flag is enabled or not.
TEST_P(DelegatedInkDisplayTest,
InkRendererRemoteGoesToSkiaRendererOrOutputSurface) {
SetUpGpuDisplay();
mojo::Remote<gfx::mojom::DelegatedInkPointRenderer> ink_renderer_remote;
display_->InitDelegatedInkPointRendererReceiver(
ink_renderer_remote.BindNewPipeAndPassReceiver());
if (GetParam() == DelegatedInkType::kPlatformInk) {
EXPECT_TRUE(skia_output_surface_
->ContainsDelegatedInkPointRendererReceiverForTesting());
EXPECT_FALSE(ink_renderer());
} else {
EXPECT_FALSE(skia_output_surface_
->ContainsDelegatedInkPointRendererReceiverForTesting());
EXPECT_TRUE(ink_renderer());
EXPECT_TRUE(ink_renderer_remote.is_bound());
}
}
using UnsupportedRendererDelegatedInkTest = DisplayTest;
// Confirm that trying to use delegated ink trails on SoftwareRenderer silently
// fails.
TEST_F(UnsupportedRendererDelegatedInkTest,
DelegatedInkSilentlyFailsOnSoftwareRenderer) {
SetUpSoftwareDisplay(RendererSettings());
display_->Initialize(client_.get(), manager_.surface_manager());
// Should silently bail early from here. Test will crash if we actually try to
// initialize the delegated ink point renderer.
mojo::Remote<gfx::mojom::DelegatedInkPointRenderer> ink_renderer_remote;
display_->InitDelegatedInkPointRendererReceiver(
ink_renderer_remote.BindNewPipeAndPassReceiver());
}
} // namespace viz