blob: 94e8740e95edd9871edd62a06ef3b80dd7304d33 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "cc/base/region.h"
#include "cc/paint/filter_operation.h"
#include "cc/paint/filter_operations.h"
#include "cc/slim/layer.h"
#include "cc/slim/nine_patch_layer.h"
#include "cc/slim/solid_color_layer.h"
#include "cc/slim/surface_layer.h"
#include "cc/slim/test_frame_sink_impl.h"
#include "cc/slim/test_layer_tree_client.h"
#include "cc/slim/test_layer_tree_impl.h"
#include "cc/slim/ui_resource_layer.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "components/viz/common/surfaces/local_surface_id.h"
#include "components/viz/common/surfaces/surface_id.h"
#include "components/viz/test/draw_quad_matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/geometry/linear_gradient.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/test/geometry_util.h"
#include "ui/gfx/presentation_feedback.h"
namespace cc::slim {
namespace {
using testing::AllOf;
using testing::ElementsAre;
class SlimLayerTreeCompositorFrameTest : public testing::Test {
public:
void SetUp() override {
layer_tree_ = std::make_unique<TestLayerTreeImpl>(&client_);
layer_tree_->SetVisible(true);
auto frame_sink = TestFrameSinkImpl::Create();
frame_sink_ = frame_sink->GetWeakPtr();
layer_tree_->SetFrameSink(std::move(frame_sink));
viewport_ = gfx::Rect(100, 100);
base::UnguessableToken token = base::UnguessableToken::Create();
local_surface_id_ = viz::LocalSurfaceId(1u, 2u, token);
EXPECT_TRUE(local_surface_id_.is_valid());
layer_tree_->SetViewportRectAndScale(
viewport_, /*device_scale_factor=*/1.0f, local_surface_id_);
}
void IncrementLocalSurfaceId() {
DCHECK(local_surface_id_.is_valid());
local_surface_id_ =
viz::LocalSurfaceId(local_surface_id_.parent_sequence_number(),
local_surface_id_.child_sequence_number() + 1,
local_surface_id_.embed_token());
DCHECK(local_surface_id_.is_valid());
}
viz::CompositorFrame ProduceFrame(
std::optional<viz::HitTestRegionList>* out_list = nullptr) {
layer_tree_->SetNeedsAnimate();
EXPECT_TRUE(layer_tree_->NeedsBeginFrames());
base::TimeTicks frame_time = base::TimeTicks::Now();
base::TimeDelta interval = viz::BeginFrameArgs::DefaultInterval();
viz::BeginFrameArgs begin_frame_args = viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE,
/*source_id=*/1, ++sequence_id_, frame_time, frame_time + interval,
interval, viz::BeginFrameArgs::NORMAL);
frame_sink_->OnBeginFrame(begin_frame_args, std::move(next_timing_details_),
/*frame_ack=*/false, {});
next_timing_details_.clear();
viz::CompositorFrame frame = frame_sink_->TakeLastFrame();
if (out_list) {
*out_list = frame_sink_->GetLastHitTestRegionList();
}
frame_sink_->DidReceiveCompositorFrameAck({});
return frame;
}
scoped_refptr<SolidColorLayer> CreateSolidColorLayer(const gfx::Size& bounds,
SkColor4f color) {
auto solid_color_layer = SolidColorLayer::Create();
solid_color_layer->SetBounds(bounds);
solid_color_layer->SetBackgroundColor(color);
solid_color_layer->SetIsDrawable(true);
return solid_color_layer;
}
void SetNextFrameTimingDetailsMap(viz::FrameTimingDetailsMap timing_map) {
next_timing_details_ = std::move(timing_map);
}
viz::FrameTimingDetails BuildFrameTimingDetails(uint32_t flags = 0) {
viz::FrameTimingDetails details;
base::TimeTicks timestamp = base::TimeTicks::Now();
base::TimeDelta interval = base::Milliseconds(16.6);
gfx::PresentationFeedback feedback(timestamp, interval, flags);
details.presentation_feedback = feedback;
return details;
}
protected:
TestLayerTreeClient client_;
std::unique_ptr<TestLayerTreeImpl> layer_tree_;
base::WeakPtr<TestFrameSinkImpl> frame_sink_;
uint64_t sequence_id_ = 0;
viz::FrameTimingDetailsMap next_timing_details_;
gfx::Rect viewport_;
viz::LocalSurfaceId local_surface_id_;
};
TEST_F(SlimLayerTreeCompositorFrameTest, CompositorFrameMetadataBasics) {
auto solid_color_layer =
CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(solid_color_layer);
uint32_t first_frame_token = 0u;
{
viz::CompositorFrame frame = ProduceFrame();
viz::CompositorFrameMetadata& metadata = frame.metadata;
EXPECT_NE(0u, metadata.frame_token);
first_frame_token = metadata.frame_token;
EXPECT_EQ(sequence_id_, metadata.begin_frame_ack.frame_id.sequence_number);
EXPECT_EQ(1.0f, metadata.device_scale_factor);
EXPECT_EQ(SkColors::kWhite, metadata.root_background_color);
EXPECT_EQ(gfx::OVERLAY_TRANSFORM_NONE, metadata.display_transform_hint);
EXPECT_EQ(std::nullopt, metadata.top_controls_visible_height);
}
IncrementLocalSurfaceId();
layer_tree_->SetViewportRectAndScale(viewport_, /*device_scale_factor=*/2.0f,
local_surface_id_);
layer_tree_->set_background_color(SkColors::kBlue);
layer_tree_->set_display_transform_hint(
gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90);
layer_tree_->UpdateTopControlsVisibleHeight(5.0f);
{
viz::CompositorFrame frame = ProduceFrame();
viz::CompositorFrameMetadata& metadata = frame.metadata;
EXPECT_NE(0u, metadata.frame_token);
EXPECT_NE(first_frame_token, metadata.frame_token);
EXPECT_EQ(sequence_id_, metadata.begin_frame_ack.frame_id.sequence_number);
EXPECT_EQ(2.0f, metadata.device_scale_factor);
EXPECT_EQ(SkColors::kBlue, metadata.root_background_color);
EXPECT_EQ(gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90,
metadata.display_transform_hint);
EXPECT_EQ(5.0f, metadata.top_controls_visible_height);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, OneSolidColorQuad) {
auto solid_color_layer =
CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(solid_color_layer);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
EXPECT_EQ(pass->output_rect, viewport_);
EXPECT_EQ(pass->damage_rect, viewport_);
EXPECT_EQ(pass->transform_to_root_target, gfx::Transform());
ASSERT_THAT(
pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()),
viz::HasOpacity(1.0f), viz::AreContentsOpaque(true))));
auto* quad = pass->quad_list.back();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_EQ(shared_quad_state->quad_layer_rect, viewport_);
EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, viewport_);
EXPECT_EQ(shared_quad_state->clip_rect, std::nullopt);
EXPECT_EQ(shared_quad_state->are_contents_opaque, true);
EXPECT_EQ(shared_quad_state->blend_mode, SkBlendMode::kSrcOver);
}
TEST_F(SlimLayerTreeCompositorFrameTest, LayerTransform) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto child = CreateSolidColorLayer(gfx::Size(10, 20), SkColors::kGreen);
root_layer->AddChild(child);
auto check_child_quad = [&](gfx::Rect expected_rect_in_root) {
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
viz::HasRect(gfx::Rect(10, 20)),
viz::HasVisibleRect(gfx::Rect(10, 20))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_))));
auto* quad = pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_EQ(shared_quad_state->quad_layer_rect, gfx::Rect(10, 20));
EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, gfx::Rect(10, 20));
gfx::Rect rect_in_root =
shared_quad_state->quad_to_target_transform.MapRect(quad->rect);
EXPECT_EQ(expected_rect_in_root, rect_in_root);
};
child->SetPosition(gfx::PointF(30.0f, 30.0f));
check_child_quad(gfx::Rect(30, 30, 10, 20));
child->SetTransform(gfx::Transform::MakeTranslation(10.0f, 10.0f));
check_child_quad(gfx::Rect(40, 40, 10, 20));
// Rotate about top left corner.
child->SetTransform(gfx::Transform::Make90degRotation());
check_child_quad(gfx::Rect(10, 30, 20, 10));
// Rotate about the center.
child->SetTransformOrigin(gfx::Point3F(5.0f, 10.0f, 0.0f));
check_child_quad(gfx::Rect(25, 35, 20, 10));
}
TEST_F(SlimLayerTreeCompositorFrameTest, ChildOrder) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
scoped_refptr<SolidColorLayer> children[] = {
CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kBlue),
CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kGreen),
CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kMagenta),
CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kRed),
CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kYellow)};
// Build tree such that quads appear in child order.
// Quads are appended post order depth first, in reverse child order.
// root <- child4 <- child3
// <- child2
// <- child1 <- child0
root_layer->AddChild(children[4]);
root_layer->AddChild(children[1]);
children[4]->AddChild(children[3]);
children[4]->AddChild(children[2]);
children[1]->AddChild(children[0]);
// Add offsets so they do not cover each other.
children[3]->SetPosition(gfx::PointF(10.0f, 10.0f));
children[2]->SetPosition(gfx::PointF(20.0f, 20.0f));
children[1]->SetPosition(gfx::PointF(30.0f, 30.0f));
children[0]->SetPosition(gfx::PointF(10.0f, 10.0f));
gfx::Point expected_origins[] = {
gfx::Point(40.0f, 40.0f), gfx::Point(30.0f, 30.0f),
gfx::Point(20.0f, 20.0f), gfx::Point(10.0f, 10.0f),
gfx::Point(00.0f, 00.0f)};
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(viz::IsSolidColorQuad(SkColors::kBlue),
viz::IsSolidColorQuad(SkColors::kGreen),
viz::IsSolidColorQuad(SkColors::kMagenta),
viz::IsSolidColorQuad(SkColors::kRed),
viz::IsSolidColorQuad(SkColors::kYellow),
viz::IsSolidColorQuad(SkColors::kGray)));
for (size_t i = 0; i < std::size(expected_origins); ++i) {
auto* quad = pass->quad_list.ElementAt(i);
EXPECT_EQ(quad->shared_quad_state->quad_to_target_transform.MapPoint(
gfx::Point()),
expected_origins[i]);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, AxisAlignedClip) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto clip_layer = Layer::Create();
clip_layer->SetBounds(gfx::Size(10, 20));
clip_layer->SetMasksToBounds(true);
auto draw_layer = CreateSolidColorLayer(gfx::Size(30, 30), SkColors::kRed);
root_layer->AddChild(clip_layer);
clip_layer->AddChild(draw_layer);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(viz::IsSolidColorQuad(SkColors::kRed),
viz::IsSolidColorQuad(SkColors::kGray)));
auto* quad = pass->quad_list.front();
ASSERT_TRUE(quad->shared_quad_state->clip_rect);
EXPECT_EQ(quad->shared_quad_state->clip_rect.value(), gfx::Rect(10, 20));
}
clip_layer->SetPosition(gfx::PointF(5, 5));
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(viz::IsSolidColorQuad(SkColors::kRed),
viz::IsSolidColorQuad(SkColors::kGray)));
auto* quad = pass->quad_list.front();
ASSERT_TRUE(quad->shared_quad_state->clip_rect);
// Clip is in target space.
EXPECT_EQ(quad->shared_quad_state->clip_rect.value(),
gfx::Rect(5, 5, 10, 20));
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, PresentationCallback) {
auto solid_color_layer =
CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(solid_color_layer);
std::optional<gfx::PresentationFeedback> feedback_opt_1;
std::optional<gfx::PresentationFeedback> feedback_opt_2;
layer_tree_->RequestPresentationTimeForNextFrame(base::BindLambdaForTesting(
[&](const gfx::PresentationFeedback& feedback) {
feedback_opt_1 = feedback;
}));
layer_tree_->RequestPresentationTimeForNextFrame(base::BindLambdaForTesting(
[&](const gfx::PresentationFeedback& feedback) {
feedback_opt_2 = feedback;
}));
viz::CompositorFrame frame1 = ProduceFrame();
viz::FrameTimingDetailsMap timing_map;
viz::FrameTimingDetails details = BuildFrameTimingDetails();
timing_map[frame1.metadata.frame_token] = details;
SetNextFrameTimingDetailsMap(std::move(timing_map));
viz::CompositorFrame frame2 = ProduceFrame();
ASSERT_TRUE(feedback_opt_1);
ASSERT_TRUE(feedback_opt_2);
EXPECT_EQ(feedback_opt_1.value(), details.presentation_feedback);
EXPECT_EQ(feedback_opt_2.value(), details.presentation_feedback);
}
TEST_F(SlimLayerTreeCompositorFrameTest, PresentationCallbackMissedFrame) {
auto solid_color_layer =
CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(solid_color_layer);
std::optional<gfx::PresentationFeedback> feedback_opt_1;
layer_tree_->RequestPresentationTimeForNextFrame(base::BindLambdaForTesting(
[&](const gfx::PresentationFeedback& feedback) {
feedback_opt_1 = feedback;
}));
viz::CompositorFrame frame1 = ProduceFrame();
std::optional<gfx::PresentationFeedback> feedback_opt_2;
layer_tree_->RequestPresentationTimeForNextFrame(base::BindLambdaForTesting(
[&](const gfx::PresentationFeedback& feedback) {
feedback_opt_2 = feedback;
}));
viz::CompositorFrame frame2 = ProduceFrame();
viz::CompositorFrame frame3 = ProduceFrame();
EXPECT_FALSE(feedback_opt_1);
EXPECT_FALSE(feedback_opt_2);
{
// Ack frame 1 which should only run the first callback.
viz::FrameTimingDetailsMap timing_map;
viz::FrameTimingDetails details = BuildFrameTimingDetails();
timing_map[frame1.metadata.frame_token] = details;
SetNextFrameTimingDetailsMap(std::move(timing_map));
viz::CompositorFrame frame4 = ProduceFrame();
EXPECT_TRUE(feedback_opt_1);
EXPECT_EQ(feedback_opt_1.value(), details.presentation_feedback);
EXPECT_FALSE(feedback_opt_2);
}
{
// Ack frame 3, skipping frame 2, which should only run the second callback.
viz::FrameTimingDetailsMap timing_map;
viz::FrameTimingDetails details = BuildFrameTimingDetails();
timing_map[frame3.metadata.frame_token] = details;
SetNextFrameTimingDetailsMap(std::move(timing_map));
viz::CompositorFrame frame4 = ProduceFrame();
ASSERT_TRUE(feedback_opt_2);
EXPECT_EQ(feedback_opt_2.value(), details.presentation_feedback);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, SuccessPresentationCallback) {
auto solid_color_layer =
CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(solid_color_layer);
std::optional<base::TimeTicks> feedback_time_opt_1;
std::optional<base::TimeTicks> feedback_time_opt_2;
layer_tree_->RequestSuccessfulPresentationTimeForNextFrame(
base::BindLambdaForTesting(
[&](base::TimeTicks timeticks) { feedback_time_opt_1 = timeticks; }));
layer_tree_->RequestSuccessfulPresentationTimeForNextFrame(
base::BindLambdaForTesting(
[&](base::TimeTicks timeticks) { feedback_time_opt_2 = timeticks; }));
viz::CompositorFrame frame1 = ProduceFrame();
viz::FrameTimingDetailsMap timing_map;
viz::FrameTimingDetails details = BuildFrameTimingDetails();
timing_map[frame1.metadata.frame_token] = details;
SetNextFrameTimingDetailsMap(std::move(timing_map));
viz::CompositorFrame frame2 = ProduceFrame();
ASSERT_TRUE(feedback_time_opt_1);
ASSERT_TRUE(feedback_time_opt_2);
EXPECT_EQ(feedback_time_opt_1.value(),
details.presentation_feedback.timestamp);
EXPECT_EQ(feedback_time_opt_2.value(),
details.presentation_feedback.timestamp);
}
TEST_F(SlimLayerTreeCompositorFrameTest,
SuccessPresentationCallbackNotCalledForFailedFrame) {
auto solid_color_layer =
CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(solid_color_layer);
std::optional<base::TimeTicks> feedback_time_opt_1;
layer_tree_->RequestSuccessfulPresentationTimeForNextFrame(
base::BindLambdaForTesting(
[&](base::TimeTicks timeticks) { feedback_time_opt_1 = timeticks; }));
viz::CompositorFrame frame1 = ProduceFrame();
viz::CompositorFrame frame2 = ProduceFrame();
std::optional<base::TimeTicks> feedback_time_opt_2;
layer_tree_->RequestSuccessfulPresentationTimeForNextFrame(
base::BindLambdaForTesting(
[&](base::TimeTicks timeticks) { feedback_time_opt_2 = timeticks; }));
viz::CompositorFrame frame3 = ProduceFrame();
// Frame 1 failed. Should not run either callback.
{
viz::FrameTimingDetailsMap timing_map;
viz::FrameTimingDetails details =
BuildFrameTimingDetails(gfx::PresentationFeedback::kFailure);
timing_map[frame1.metadata.frame_token] = details;
SetNextFrameTimingDetailsMap(std::move(timing_map));
viz::CompositorFrame frame4 = ProduceFrame();
EXPECT_FALSE(feedback_time_opt_1);
EXPECT_FALSE(feedback_time_opt_2);
}
// Successful feedback for frame 2. Should run callback 1 but not 2.
{
viz::FrameTimingDetailsMap timing_map;
viz::FrameTimingDetails details = BuildFrameTimingDetails();
timing_map[frame2.metadata.frame_token] = details;
SetNextFrameTimingDetailsMap(std::move(timing_map));
viz::CompositorFrame frame5 = ProduceFrame();
ASSERT_TRUE(feedback_time_opt_1);
EXPECT_EQ(feedback_time_opt_1.value(),
details.presentation_feedback.timestamp);
ASSERT_FALSE(feedback_time_opt_2);
}
// Successful feedback for frame 3. Should run 2.
{
viz::FrameTimingDetailsMap timing_map;
viz::FrameTimingDetails details = BuildFrameTimingDetails();
timing_map[frame3.metadata.frame_token] = details;
SetNextFrameTimingDetailsMap(std::move(timing_map));
viz::CompositorFrame frame5 = ProduceFrame();
ASSERT_TRUE(feedback_time_opt_2);
EXPECT_EQ(feedback_time_opt_2.value(),
details.presentation_feedback.timestamp);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, CopyOutputRequest) {
auto solid_color_layer =
CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(solid_color_layer);
{
viz::CompositorFrame frame = ProduceFrame();
EXPECT_FALSE(layer_tree_->NeedsBeginFrames());
}
auto copy_request_no_source_1 = std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA,
viz::CopyOutputRequest::ResultDestination::kSystemMemory,
base::DoNothing());
auto copy_request_no_source_2 = std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA,
viz::CopyOutputRequest::ResultDestination::kSystemMemory,
base::DoNothing());
base::UnguessableToken token = base::UnguessableToken::Create();
auto copy_request_with_source = std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA,
viz::CopyOutputRequest::ResultDestination::kSystemMemory,
base::DoNothing());
copy_request_with_source->set_source(token);
auto copy_request_with_same_source = std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA,
viz::CopyOutputRequest::ResultDestination::kSystemMemory,
base::DoNothing());
copy_request_with_same_source->set_source(token);
base::UnguessableToken token2 = base::UnguessableToken::Create();
auto copy_request_with_difference_source =
std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA,
viz::CopyOutputRequest::ResultDestination::kSystemMemory,
base::DoNothing());
copy_request_with_difference_source->set_source(token2);
layer_tree_->RequestCopyOfOutput(std::move(copy_request_no_source_1));
EXPECT_TRUE(layer_tree_->NeedsBeginFrames());
layer_tree_->RequestCopyOfOutput(std::move(copy_request_no_source_2));
layer_tree_->RequestCopyOfOutput(std::move(copy_request_with_source));
layer_tree_->RequestCopyOfOutput(std::move(copy_request_with_same_source));
layer_tree_->RequestCopyOfOutput(
std::move(copy_request_with_difference_source));
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_EQ(pass->copy_requests.size(), 4u);
EXPECT_TRUE(pass->copy_requests[0]);
EXPECT_TRUE(pass->copy_requests[1]);
EXPECT_TRUE(pass->copy_requests[2]);
EXPECT_TRUE(pass->copy_requests[3]);
}
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_EQ(pass->copy_requests.size(), 0u);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, UIResourceLayerAppendQuads) {
auto ui_resource_layer = UIResourceLayer::Create();
ui_resource_layer->SetBounds(viewport_.size());
ui_resource_layer->SetIsDrawable(true);
ui_resource_layer->SetContentsOpaque(true);
layer_tree_->SetRoot(ui_resource_layer);
viz::ResourceId first_resource_id = viz::kInvalidResourceId;
{
auto image_info =
SkImageInfo::Make(1, 1, kN32_SkColorType, kPremul_SkAlphaType);
SkBitmap bitmap;
bitmap.allocPixels(image_info);
bitmap.setImmutable();
ui_resource_layer->SetBitmap(bitmap);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsTextureQuad(), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
const viz::TextureDrawQuad* texture_quad =
viz::TextureDrawQuad::MaterialCast(pass->quad_list.front());
EXPECT_TRUE(texture_quad->needs_blending);
EXPECT_NE(viz::kInvalidResourceId, texture_quad->resource_id());
EXPECT_EQ(gfx::PointF(0.0f, 0.0f), texture_quad->uv_top_left);
EXPECT_EQ(gfx::PointF(1.0f, 1.0f), texture_quad->uv_bottom_right);
EXPECT_EQ(1.0f, texture_quad->vertex_opacity[0]);
EXPECT_EQ(1.0f, texture_quad->vertex_opacity[1]);
EXPECT_EQ(1.0f, texture_quad->vertex_opacity[2]);
EXPECT_EQ(1.0f, texture_quad->vertex_opacity[3]);
ASSERT_EQ(frame.resource_list.size(), 1u);
EXPECT_EQ(frame.resource_list[0].id, texture_quad->resource_id());
EXPECT_EQ(frame.resource_list[0].size, gfx::Size(1, 1));
first_resource_id = texture_quad->resource_id();
ASSERT_EQ(frame_sink_->uploaded_resources().size(), 1u);
EXPECT_EQ(frame_sink_->uploaded_resources().begin()->second.viz_resource_id,
texture_quad->resource_id());
}
ui_resource_layer->SetUV(gfx::PointF(0.25f, 0.25f),
gfx::PointF(0.75f, 0.75f));
ui_resource_layer->SetVertexOpacity(0.1f, 0.2f, 0.3f, 0.4f);
{
auto image_info =
SkImageInfo::Make(2, 2, kN32_SkColorType, kPremul_SkAlphaType);
SkBitmap bitmap;
bitmap.allocPixels(image_info);
bitmap.setImmutable();
ui_resource_layer->SetBitmap(bitmap);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsTextureQuad(), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
const viz::TextureDrawQuad* texture_quad =
viz::TextureDrawQuad::MaterialCast(pass->quad_list.front());
EXPECT_TRUE(texture_quad->needs_blending);
EXPECT_NE(viz::kInvalidResourceId, texture_quad->resource_id());
EXPECT_EQ(gfx::PointF(0.25f, 0.25f), texture_quad->uv_top_left);
EXPECT_EQ(gfx::PointF(0.75f, 0.75f), texture_quad->uv_bottom_right);
EXPECT_EQ(0.1f, texture_quad->vertex_opacity[0]);
EXPECT_EQ(0.2f, texture_quad->vertex_opacity[1]);
EXPECT_EQ(0.3f, texture_quad->vertex_opacity[2]);
EXPECT_EQ(0.4f, texture_quad->vertex_opacity[3]);
ASSERT_EQ(frame.resource_list.size(), 1u);
EXPECT_EQ(frame.resource_list[0].id, texture_quad->resource_id());
EXPECT_EQ(frame.resource_list[0].size, gfx::Size(2, 2));
EXPECT_NE(first_resource_id, texture_quad->resource_id());
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, ReclaimResources) {
constexpr size_t kNumLayers = 6;
std::vector<scoped_refptr<UIResourceLayer>> layers;
for (size_t i = 0; i < kNumLayers; ++i) {
layers.push_back(UIResourceLayer::Create());
layers[i]->SetBounds(viewport_.size());
layers[i]->SetIsDrawable(true);
if (i == 0u) {
layer_tree_->SetRoot(layers[i]);
} else {
layers[i - 1]->AddChild(layers[i]);
}
auto image_info =
SkImageInfo::Make(1, 1, kN32_SkColorType, kPremul_SkAlphaType);
SkBitmap bitmap;
bitmap.allocPixels(image_info);
bitmap.setImmutable();
layers[i]->SetBitmap(bitmap);
}
viz::CompositorFrame frame = ProduceFrame();
EXPECT_EQ(frame.resource_list.size(), kNumLayers);
for (size_t i = 0; i < kNumLayers; ++i) {
EXPECT_TRUE(frame_sink_->client_resource_provider()->InUseByConsumer(
frame.resource_list[i].id));
}
// Return every other resource.
std::vector<viz::ReturnedResource> returned_resources;
for (size_t i = 0; i < kNumLayers; i += 2) {
returned_resources.push_back(frame.resource_list[i].ToReturnedResource());
}
frame_sink_->ReclaimResources(std::move(returned_resources));
for (size_t i = 0; i < kNumLayers; i += 2) {
EXPECT_FALSE(frame_sink_->client_resource_provider()->InUseByConsumer(
frame.resource_list[i].id));
}
for (size_t i = 1; i < kNumLayers; i += 2) {
EXPECT_TRUE(frame_sink_->client_resource_provider()->InUseByConsumer(
frame.resource_list[i].id));
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, NinePatchLayerAppendQuads) {
auto nine_patch_layer = NinePatchLayer::Create();
nine_patch_layer->SetBounds(viewport_.size());
nine_patch_layer->SetIsDrawable(true);
nine_patch_layer->SetContentsOpaque(true);
layer_tree_->SetRoot(nine_patch_layer);
auto image_info =
SkImageInfo::Make(10, 10, kN32_SkColorType, kPremul_SkAlphaType);
SkBitmap bitmap;
bitmap.allocPixels(image_info);
bitmap.setImmutable();
nine_patch_layer->SetBitmap(bitmap);
nine_patch_layer->SetBorder(gfx::Rect(10, 10, 20, 20)); // 10 pixel border.
nine_patch_layer->SetAperture(gfx::Rect(2, 2, 6, 6));
nine_patch_layer->SetFillCenter(true);
nine_patch_layer->SetNearestNeighbor(true);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.resource_list.size(), 1u);
EXPECT_EQ(frame.resource_list[0].size, gfx::Size(10, 10));
ASSERT_EQ(frame_sink_->uploaded_resources().size(), 1u);
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(
pass->quad_list,
ElementsAre(
// Top left.
AllOf(viz::IsTextureQuad(), viz::HasRect(gfx::Rect(10, 10)),
viz::HasVisibleRect(gfx::Rect(10, 10))),
// Top right.
AllOf(viz::IsTextureQuad(), viz::HasRect(gfx::Rect(90, 0, 10, 10)),
viz::HasVisibleRect(gfx::Rect(90, 0, 10, 10))),
// Bottom left.
AllOf(viz::IsTextureQuad(), viz::HasRect(gfx::Rect(0, 90, 10, 10)),
viz::HasVisibleRect(gfx::Rect(0, 90, 10, 10))),
// Bottom right.
AllOf(viz::IsTextureQuad(), viz::HasRect(gfx::Rect(90, 90, 10, 10)),
viz::HasVisibleRect(gfx::Rect(90, 90, 10, 10))),
// Top.
AllOf(viz::IsTextureQuad(), viz::HasRect(gfx::Rect(10, 0, 80, 10)),
viz::HasVisibleRect(gfx::Rect(10, 0, 80, 10))),
// Left.
AllOf(viz::IsTextureQuad(), viz::HasRect(gfx::Rect(0, 10, 10, 80)),
viz::HasVisibleRect(gfx::Rect(0, 10, 10, 80))),
// Right.
AllOf(viz::IsTextureQuad(), viz::HasRect(gfx::Rect(90, 10, 10, 80)),
viz::HasVisibleRect(gfx::Rect(90, 10, 10, 80))),
// Bottom.
AllOf(viz::IsTextureQuad(), viz::HasRect(gfx::Rect(10, 90, 80, 10)),
viz::HasVisibleRect(gfx::Rect(10, 90, 80, 10))),
// Center.
AllOf(viz::IsTextureQuad(),
viz::HasRect(gfx::Rect(10, 10, 80, 80)))));
gfx::PointF expected_uv_top_left[] = {
gfx::PointF(0.0f, 0.0f), // Top left.
gfx::PointF(0.8f, 0.0f), // Top right.
gfx::PointF(0.0f, 0.8f), // Bottom left.
gfx::PointF(0.8f, 0.8f), // Bottom right.
gfx::PointF(0.2f, 0.0f), // Top.
gfx::PointF(0.0f, 0.2f), // Left.
gfx::PointF(0.8f, 0.2f), // Right.
gfx::PointF(0.2f, 0.8f), // Bottom.
gfx::PointF(0.2f, 0.2f), // Center.
};
gfx::PointF expected_uv_bottom_right[] = {
gfx::PointF(0.2f, 0.2f), // Top left.
gfx::PointF(1.0f, 0.2f), // Top right.
gfx::PointF(0.2f, 1.0f), // Bottom left.
gfx::PointF(1.0f, 1.0f), // Bottom right.
gfx::PointF(0.8f, 0.2f), // Top.
gfx::PointF(0.2f, 0.8f), // Left.
gfx::PointF(1.0f, 0.8f), // Right.
gfx::PointF(0.8f, 1.0f), // Bottom.
gfx::PointF(0.8f, 0.8f), // Center.
};
for (size_t i = 0; i < std::size(expected_uv_top_left); ++i) {
const viz::TextureDrawQuad* texture_quad =
viz::TextureDrawQuad::MaterialCast(pass->quad_list.ElementAt(i));
EXPECT_NE(viz::kInvalidResourceId, texture_quad->resource_id());
EXPECT_TRUE(texture_quad->nearest_neighbor);
EXPECT_EQ(expected_uv_top_left[i], texture_quad->uv_top_left);
EXPECT_EQ(expected_uv_bottom_right[i], texture_quad->uv_bottom_right);
EXPECT_EQ(1.0f, texture_quad->vertex_opacity[0]);
EXPECT_EQ(1.0f, texture_quad->vertex_opacity[1]);
EXPECT_EQ(1.0f, texture_quad->vertex_opacity[2]);
EXPECT_EQ(1.0f, texture_quad->vertex_opacity[3]);
EXPECT_EQ(frame.resource_list[0].id, texture_quad->resource_id());
EXPECT_EQ(frame_sink_->uploaded_resources().begin()->second.viz_resource_id,
texture_quad->resource_id());
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, SurfaceLayerAppendQuads) {
auto surface_layer = SurfaceLayer::Create();
surface_layer->SetBounds(viewport_.size());
surface_layer->SetIsDrawable(true);
surface_layer->SetContentsOpaque(true);
layer_tree_->SetRoot(surface_layer);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(
pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_))));
}
base::UnguessableToken token = base::UnguessableToken::Create();
viz::SurfaceId start(viz::FrameSinkId(1u, 2u),
viz::LocalSurfaceId(3u, 4u, token));
{
viz::SurfaceId end(viz::FrameSinkId(1u, 2u),
viz::LocalSurfaceId(5u, 6u, token));
cc::DeadlinePolicy deadline_policy =
cc::DeadlinePolicy::UseDefaultDeadline();
surface_layer->SetOldestAcceptableFallback(start);
surface_layer->SetSurfaceId(end, deadline_policy);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSurfaceQuad(), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_))));
auto* quad = viz::SurfaceDrawQuad::MaterialCast(pass->quad_list.back());
EXPECT_EQ(quad->surface_range, viz::SurfaceRange(start, end));
EXPECT_FALSE(quad->stretch_content_to_fill_bounds);
EXPECT_FALSE(quad->is_reflection);
EXPECT_TRUE(quad->allow_merge);
viz::CompositorFrameMetadata& metadata = frame.metadata;
EXPECT_EQ(metadata.referenced_surfaces,
std::vector<viz::SurfaceRange>{viz::SurfaceRange(start, end)});
EXPECT_EQ(metadata.activation_dependencies,
std::vector<viz::SurfaceId>{end});
EXPECT_FALSE(metadata.deadline.deadline_in_frames());
EXPECT_TRUE(metadata.deadline.use_default_lower_bound_deadline());
}
{
viz::SurfaceId end(viz::FrameSinkId(1u, 2u),
viz::LocalSurfaceId(5u, 7u, token));
cc::DeadlinePolicy deadline_policy =
cc::DeadlinePolicy::UseSpecifiedDeadline(2u);
surface_layer->SetSurfaceId(end, deadline_policy);
surface_layer->SetStretchContentToFillBounds(true);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSurfaceQuad(), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_))));
auto* quad = viz::SurfaceDrawQuad::MaterialCast(pass->quad_list.back());
EXPECT_EQ(quad->surface_range, viz::SurfaceRange(start, end));
EXPECT_TRUE(quad->stretch_content_to_fill_bounds);
viz::CompositorFrameMetadata& metadata = frame.metadata;
EXPECT_EQ(metadata.referenced_surfaces,
std::vector<viz::SurfaceRange>{viz::SurfaceRange(start, end)});
EXPECT_EQ(metadata.activation_dependencies,
std::vector<viz::SurfaceId>{end});
EXPECT_EQ(metadata.deadline.deadline_in_frames(), 2u);
EXPECT_FALSE(metadata.deadline.use_default_lower_bound_deadline());
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, SimpleHitTestRegionList) {
auto surface_layer = SurfaceLayer::Create();
surface_layer->SetBounds(viewport_.size());
surface_layer->SetIsDrawable(true);
layer_tree_->SetRoot(surface_layer);
{
base::UnguessableToken token = base::UnguessableToken::Create();
viz::SurfaceId surface_id(viz::FrameSinkId(1u, 2u),
viz::LocalSurfaceId(3u, 4u, token));
cc::DeadlinePolicy deadline_policy =
cc::DeadlinePolicy::UseDefaultDeadline();
surface_layer->SetSurfaceId(surface_id, deadline_policy);
std::optional<viz::HitTestRegionList> hit_test_region_list;
viz::CompositorFrame frame = ProduceFrame(&hit_test_region_list);
ASSERT_TRUE(hit_test_region_list);
EXPECT_EQ(hit_test_region_list->bounds, viewport_);
ASSERT_EQ(hit_test_region_list->regions.size(), 1u);
auto& hit_test_region = hit_test_region_list->regions.front();
EXPECT_EQ(hit_test_region.frame_sink_id, viz::FrameSinkId(1u, 2u));
EXPECT_EQ(hit_test_region.rect, viewport_);
EXPECT_EQ(hit_test_region.transform, gfx::Transform());
}
auto child_surface_layer = SurfaceLayer::Create();
surface_layer->AddChild(child_surface_layer);
child_surface_layer->SetBounds(gfx::Size(10, 10));
child_surface_layer->SetIsDrawable(true);
child_surface_layer->SetPosition(gfx::PointF(10.0f, 10.0f));
child_surface_layer->SetTransformOrigin(gfx::Point3F(5.0f, 5.0f, 0.0f));
gfx::Transform transform;
transform.Rotate(45.0);
child_surface_layer->SetTransform(transform);
base::UnguessableToken token = base::UnguessableToken::Create();
viz::SurfaceId surface_id(viz::FrameSinkId(2u, 3u),
viz::LocalSurfaceId(4u, 5u, token));
cc::DeadlinePolicy deadline_policy = cc::DeadlinePolicy::UseDefaultDeadline();
child_surface_layer->SetSurfaceId(surface_id, deadline_policy);
{
std::optional<viz::HitTestRegionList> hit_test_region_list;
viz::CompositorFrame frame = ProduceFrame(&hit_test_region_list);
ASSERT_TRUE(hit_test_region_list);
EXPECT_EQ(hit_test_region_list->bounds, viewport_);
ASSERT_EQ(hit_test_region_list->regions.size(), 2u);
auto& root_region = hit_test_region_list->regions.back();
EXPECT_EQ(root_region.frame_sink_id, viz::FrameSinkId(1u, 2u));
EXPECT_EQ(root_region.rect, viewport_);
EXPECT_EQ(root_region.transform, gfx::Transform());
auto& child_region = hit_test_region_list->regions.front();
EXPECT_EQ(child_region.frame_sink_id, viz::FrameSinkId(2u, 3u));
EXPECT_EQ(child_region.rect, gfx::Rect(10, 10));
gfx::Transform expected_transform =
gfx::Transform::MakeTranslation(5.0f, 5.0f);
expected_transform.Rotate(-45.0);
expected_transform.Translate(-5.0f, -5.0f);
expected_transform.Translate(-10.0f, -10.0f);
EXPECT_TRANSFORM_NEAR(child_region.transform, expected_transform, 1e-15);
EXPECT_TRUE(child_region.flags | viz::HitTestRegionFlags::kHitTestAsk);
EXPECT_TRUE(child_region.async_hit_test_reasons |
viz::AsyncHitTestReasons::kIrregularClip);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, HitTestRegionInNonRootPass) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto filter_layer = Layer::Create();
filter_layer->SetBounds(gfx::Size(50, 50));
filter_layer->SetPosition(gfx::PointF(10.0f, 10.0f));
// Add a filter to force non-root render pass.
filter_layer->SetFilters({cc::slim::Filter::CreateBrightness(0.5f)});
auto surface_layer = SurfaceLayer::Create();
surface_layer->SetBounds(gfx::Size(100, 100));
surface_layer->SetIsDrawable(true);
surface_layer->SetTransform(gfx::Transform::MakeScale(0.5));
base::UnguessableToken token = base::UnguessableToken::Create();
viz::SurfaceId surface_id(viz::FrameSinkId(1u, 2u),
viz::LocalSurfaceId(3u, 4u, token));
cc::DeadlinePolicy deadline_policy = cc::DeadlinePolicy::UseDefaultDeadline();
surface_layer->SetSurfaceId(surface_id, deadline_policy);
root_layer->AddChild(filter_layer);
filter_layer->AddChild(surface_layer);
{
std::optional<viz::HitTestRegionList> hit_test_region_list;
viz::CompositorFrame frame = ProduceFrame(&hit_test_region_list);
ASSERT_TRUE(hit_test_region_list);
EXPECT_EQ(hit_test_region_list->bounds, viewport_);
ASSERT_EQ(hit_test_region_list->regions.size(), 1u);
auto& hit_test_region = hit_test_region_list->regions.front();
EXPECT_EQ(hit_test_region.frame_sink_id, viz::FrameSinkId(1u, 2u));
EXPECT_EQ(hit_test_region.rect, gfx::Rect(100, 100));
EXPECT_EQ(hit_test_region.transform,
gfx::Transform::MakeScale(2.0f) *
gfx::Transform::MakeTranslation(-10.0f, -10.0f));
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, NonInvertibleTransform) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto child_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kRed);
child_layer->SetTransform(gfx::Transform::MakeScale(0.0f));
root_layer->AddChild(child_layer);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
// Check only child layer does not generate a quad.
ASSERT_THAT(
pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
}
TEST_F(SlimLayerTreeCompositorFrameTest, VisibleRect) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto clip_and_scale_layer = Layer::Create();
clip_and_scale_layer->SetMasksToBounds(true);
// Odd size so halving scaling it by 0.5 results in non-integer rect.
clip_and_scale_layer->SetBounds(gfx::Size(49, 49));
clip_and_scale_layer->SetTransform(gfx::Transform::MakeScale(0.5f));
root_layer->AddChild(clip_and_scale_layer);
auto clipped_layer = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
clip_and_scale_layer->AddChild(clipped_layer);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(
pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(49, 49)),
viz::HasTransform(gfx::Transform::MakeScale(0.5f))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
auto* child_quad = pass->quad_list.front();
EXPECT_EQ(child_quad->shared_quad_state->quad_layer_rect, gfx::Rect(50, 50));
EXPECT_EQ(child_quad->shared_quad_state->visible_quad_layer_rect,
gfx::Rect(49, 49));
ASSERT_TRUE(child_quad->shared_quad_state->clip_rect);
// `clip_rect` in target pass space should be rounded up.
EXPECT_EQ(child_quad->shared_quad_state->clip_rect, gfx::Rect(25, 25));
}
TEST_F(SlimLayerTreeCompositorFrameTest, CompletelyClippedLayer) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto clip_and_scale_layer = Layer::Create();
clip_and_scale_layer->SetMasksToBounds(true);
clip_and_scale_layer->SetBounds(gfx::Size(50, 50));
root_layer->AddChild(clip_and_scale_layer);
auto clipped_layer = CreateSolidColorLayer(gfx::Size(25, 25), SkColors::kRed);
clipped_layer->SetPosition(gfx::PointF(60.0f, 60.0f));
clip_and_scale_layer->AddChild(clipped_layer);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(
pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
}
TEST_F(SlimLayerTreeCompositorFrameTest, NonAxisAlignedClip) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
// Clip is 50x50 rotated by 45 degrees about the center.
auto clip_layer = cc::slim::Layer::Create();
clip_layer->SetMasksToBounds(true);
clip_layer->SetBounds(gfx::Size(50, 50));
clip_layer->SetTransformOrigin(gfx::Point3F(25.0f, 25.0f, 0.0f));
gfx::Transform transform;
transform.Rotate(45);
clip_layer->SetTransform(transform);
root_layer->AddChild(clip_layer);
// Drawing layer is 80x80 larger than the clip.
auto solid_color_layer =
CreateSolidColorLayer(gfx::Size(80, 80), SkColors::kRed);
clip_layer->AddChild(std::move(solid_color_layer));
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
EXPECT_EQ(child_pass->output_rect, gfx::Rect(50, 50));
EXPECT_EQ(child_pass->damage_rect, gfx::Rect(50, 50));
gfx::Transform child_pass_transform =
gfx::Transform::MakeTranslation(25.0f, 25.0f);
child_pass_transform.PreConcat(transform);
child_pass_transform.PreConcat(
gfx::Transform::MakeTranslation(-25.0f, -25.0f));
EXPECT_EQ(child_pass->transform_to_root_target, child_pass_transform);
EXPECT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(80, 80)),
viz::HasVisibleRect(gfx::Rect(50, 50)),
viz::HasTransform(gfx::Transform()))));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50)),
viz::HasTransform(child_pass_transform)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
auto* render_pass_quad = viz::CompositorRenderPassDrawQuad::MaterialCast(
root_pass->quad_list.ElementAt(0));
auto* shared_quad_state = render_pass_quad->shared_quad_state;
EXPECT_EQ(shared_quad_state->quad_layer_rect, gfx::Rect(50, 50));
EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, gfx::Rect(50, 50));
EXPECT_EQ(shared_quad_state->clip_rect, std::nullopt);
}
TEST_F(SlimLayerTreeCompositorFrameTest, ChildPassOutputRect) {
// Tests that child render pass is only sized to areas with content.
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
// Clip is 50x50 rotated by 45 degrees about the center.
auto clip_layer = cc::slim::Layer::Create();
clip_layer->SetMasksToBounds(true);
clip_layer->SetBounds(gfx::Size(50, 50));
clip_layer->SetTransformOrigin(gfx::Point3F(25.0f, 25.0f, 0.0f));
gfx::Transform transform;
transform.Rotate(45);
clip_layer->SetTransform(transform);
root_layer->AddChild(clip_layer);
// Drawing layer is 80x80 offset by 20,20.
auto solid_color_layer =
CreateSolidColorLayer(gfx::Size(80, 80), SkColors::kRed);
solid_color_layer->SetPosition(gfx::PointF(20.0f, 20.0f));
clip_layer->AddChild(std::move(solid_color_layer));
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
// Child pass should only have size 30x30.
EXPECT_EQ(child_pass->output_rect, gfx::Rect(20, 20, 30, 30));
EXPECT_EQ(child_pass->damage_rect, gfx::Rect(20, 20, 30, 30));
gfx::Transform child_pass_transform =
gfx::Transform::MakeTranslation(25.0f, 25.0f);
child_pass_transform.PreConcat(transform);
child_pass_transform.PreConcat(
gfx::Transform::MakeTranslation(-25.0f, -25.0f));
EXPECT_EQ(child_pass->transform_to_root_target, child_pass_transform);
ASSERT_THAT(
child_pass->quad_list,
ElementsAre(AllOf(
viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(80, 80)),
// Visible rect is clipped.
viz::HasVisibleRect(gfx::Rect(30, 30)),
viz::HasTransform(gfx::Transform::MakeTranslation(20.0f, 20.0f)))));
{
// SharedQuadState should match the quad.
auto* shared_quad_state =
child_pass->quad_list.ElementAt(0)->shared_quad_state;
EXPECT_EQ(shared_quad_state->quad_layer_rect, gfx::Rect(80, 80));
EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, gfx::Rect(30, 30));
}
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(20, 20, 30, 30)),
viz::HasVisibleRect(gfx::Rect(20, 20, 30, 30)),
viz::HasTransform(child_pass_transform)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
{
auto* render_pass_quad = viz::CompositorRenderPassDrawQuad::MaterialCast(
root_pass->quad_list.ElementAt(0));
auto* shared_quad_state = render_pass_quad->shared_quad_state;
EXPECT_EQ(shared_quad_state->quad_layer_rect, gfx::Rect(20, 20, 30, 30));
EXPECT_EQ(shared_quad_state->visible_quad_layer_rect,
gfx::Rect(20, 20, 30, 30));
EXPECT_EQ(shared_quad_state->clip_rect, std::nullopt);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, Filters) {
// Also tests that scale down applies to child pass.
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
// Child layer has filters so require a child render pass.
// Child is scaled down and translated.
auto solid_color_layer =
CreateSolidColorLayer(gfx::Size(80, 80), SkColors::kRed);
solid_color_layer->SetTransform(gfx::Transform::MakeScale(0.5f));
solid_color_layer->SetFilters({cc::slim::Filter::CreateBrightness(0.5f)});
solid_color_layer->SetPosition(gfx::PointF(10.0f, 10.0f));
root_layer->AddChild(std::move(solid_color_layer));
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
// Child pass should be scaled down.
EXPECT_EQ(child_pass->output_rect, gfx::Rect(40, 40));
EXPECT_EQ(child_pass->damage_rect, gfx::Rect(40, 40));
// Pass is translated.
gfx::Transform child_pass_transform =
gfx::Transform::MakeTranslation(10.0f, 10.0f);
EXPECT_EQ(child_pass->transform_to_root_target, child_pass_transform);
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(
viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(80, 80)),
// Visible rect is clipped.
viz::HasVisibleRect(gfx::Rect(80, 80)),
// Scaled down to fit the child pass.
viz::HasTransform(gfx::Transform::MakeScale(0.5f, 0.5f)))));
{
// SharedQuadState should match the quad.
auto* shared_quad_state =
child_pass->quad_list.ElementAt(0)->shared_quad_state;
EXPECT_EQ(shared_quad_state->quad_layer_rect, gfx::Rect(80, 80));
EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, gfx::Rect(80, 80));
}
EXPECT_THAT(child_pass->filters.operations(),
ElementsAre(cc::FilterOperation::CreateBrightnessFilter(0.5f)));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
// Render pass quad matches pass size.
viz::HasRect(gfx::Rect(40, 40)),
viz::HasVisibleRect(gfx::Rect(40, 40)),
// Quad is only translated.
viz::HasTransform(
gfx::Transform::MakeTranslation(10.0f, 10.0f))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
{
auto* render_pass_quad = viz::CompositorRenderPassDrawQuad::MaterialCast(
root_pass->quad_list.ElementAt(0));
auto* shared_quad_state = render_pass_quad->shared_quad_state;
EXPECT_EQ(shared_quad_state->quad_layer_rect, gfx::Rect(40, 40));
EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, gfx::Rect(40, 40));
EXPECT_EQ(shared_quad_state->clip_rect, std::nullopt);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, FiltersOnNonDrawingLayer) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto filter_layer = cc::slim::Layer::Create();
filter_layer->SetFilters({cc::slim::Filter::CreateBrightness(0.5f)});
auto solid_color_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
filter_layer->AddChild(solid_color_layer);
root_layer->AddChild(filter_layer);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50)))));
EXPECT_EQ(child_pass->output_rect, gfx::Rect(50, 50));
EXPECT_THAT(child_pass->filters.operations(),
ElementsAre(cc::FilterOperation::CreateBrightnessFilter(0.5f)));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50))),
viz::IsSolidColorQuad(SkColors::kGray)));
}
// Clip the child pass.
filter_layer->SetBounds(gfx::Size(25, 25));
filter_layer->SetMasksToBounds(true);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(25, 25)))));
EXPECT_EQ(child_pass->output_rect, gfx::Rect(25, 25));
EXPECT_THAT(child_pass->filters.operations(),
ElementsAre(cc::FilterOperation::CreateBrightnessFilter(0.5f)));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(25, 25)),
viz::HasVisibleRect(gfx::Rect(25, 25))),
viz::IsSolidColorQuad(SkColors::kGray)));
}
// Completely clip the child pass.
filter_layer->SetBounds(gfx::Size(0, 0));
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(root_pass->quad_list,
ElementsAre(viz::IsSolidColorQuad(SkColors::kGray)));
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, Opacity) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
// Child will require a render pass to blend correctly.
auto child_layer = CreateSolidColorLayer(gfx::Size(80, 80), SkColors::kRed);
child_layer->SetOpacity(0.75f);
// Grand child does not need another render pass because it does not have
// 2 drawing layers.
auto grand_child_layer =
CreateSolidColorLayer(gfx::Size(40, 40), SkColors::kGreen);
grand_child_layer->SetOpacity(0.5f);
child_layer->AddChild(std::move(grand_child_layer));
root_layer->AddChild(std::move(child_layer));
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
EXPECT_EQ(child_pass->output_rect, gfx::Rect(80, 80));
EXPECT_EQ(child_pass->damage_rect, gfx::Rect(80, 80));
EXPECT_EQ(child_pass->transform_to_root_target, gfx::Transform());
ASSERT_THAT(
child_pass->quad_list,
ElementsAre(
AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
viz::HasRect(gfx::Rect(40, 40)),
viz::HasVisibleRect(gfx::Rect(40, 40)),
viz::HasTransform(gfx::Transform()),
// The pass is drawn with the child_layer's opacity, so there is
// no multiplicative opacity here.
viz::HasOpacity(0.5f)),
AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(80, 80)),
viz::HasVisibleRect(gfx::Rect(80, 80)),
viz::HasTransform(gfx::Transform()), viz::HasOpacity(1.0f))));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(
AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(80, 80)),
viz::HasVisibleRect(gfx::Rect(80, 80)),
viz::HasTransform(gfx::Transform()), viz::HasOpacity(0.75f)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
}
TEST_F(SlimLayerTreeCompositorFrameTest, SkipZeroOpacitySubtree) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto child_layer = CreateSolidColorLayer(gfx::Size(80, 80), SkColors::kRed);
child_layer->SetOpacity(0.0f);
auto grand_child_layer =
CreateSolidColorLayer(gfx::Size(40, 40), SkColors::kGreen);
child_layer->AddChild(std::move(grand_child_layer));
root_layer->AddChild(std::move(child_layer));
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& root_pass = frame.render_pass_list.back();
EXPECT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
}
TEST_F(SlimLayerTreeCompositorFrameTest, SimpleOcclusion) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto partially_occluded_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
partially_occluded_layer->SetPosition(gfx::PointF(25.0f, 25.0f));
// Occlude top 10 pixels.
auto sibling_occlusion_layer =
CreateSolidColorLayer(gfx::Size(50, 10), SkColors::kGreen);
// Position relative to root.
sibling_occlusion_layer->SetPosition(gfx::PointF(25.0f, 25.0f));
// Occlude the next top 10 pixels.
auto child_occlusion_layer =
CreateSolidColorLayer(gfx::Size(50, 10), SkColors::kBlue);
// Position relative to `partially_occluded_layer`.
child_occlusion_layer->SetPosition(gfx::PointF(0.0f, 10.0f));
partially_occluded_layer->AddChild(std::move(child_occlusion_layer));
root_layer->AddChild(std::move(partially_occluded_layer));
root_layer->AddChild(std::move(sibling_occlusion_layer));
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(viz::IsSolidColorQuad(SkColors::kGreen),
viz::IsSolidColorQuad(SkColors::kBlue),
AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(0, 20, 50, 30))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_))));
}
TEST_F(SlimLayerTreeCompositorFrameTest, OcclusionWithNonOpaqueLayer) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto lower_layer = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
root_layer->AddChild(lower_layer);
// Middle layer is not opaque so should not contribute to occlusion.
auto middle_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kGreen);
middle_layer->SetPosition(gfx::PointF(25.0f, 0.0f));
middle_layer->SetOpacity(0.5f);
root_layer->AddChild(middle_layer);
// Top layer should partially occlude middle layer.
auto top_layer = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kBlue);
top_layer->SetPosition(gfx::PointF(50.0f, 0.0f));
root_layer->AddChild(top_layer);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
EXPECT_THAT(
pass->quad_list,
ElementsAre(
AllOf(viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasVisibleRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
// Middle layer occluded on the right by top layer.
viz::HasVisibleRect(gfx::Rect(25, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kRed),
// Lower layer not occluded by non-opaque middle layer.
viz::HasVisibleRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray), viz::HasRect(viewport_),
// Top half is occluded by lower and top layer.
viz::HasVisibleRect(gfx::Rect(0, 50, 100, 50)))));
}
TEST_F(SlimLayerTreeCompositorFrameTest, OcclusionWithRenderPass) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto child_pass_root = Layer::Create();
child_pass_root->SetFilters({cc::slim::Filter::CreateBrightness(0.5f)});
// Set size and scale to half of viewport.
child_pass_root->SetBounds(gfx::Size(200, 100));
child_pass_root->SetTransform(gfx::Transform::MakeScale(0.5f));
child_pass_root->SetPosition(gfx::PointF(0.0f, 50.0f));
root_layer->AddChild(child_pass_root);
auto child_pass_layer =
CreateSolidColorLayer(gfx::Size(200, 100), SkColors::kRed);
child_pass_root->AddChild(child_pass_layer);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(200, 100)),
viz::HasVisibleRect(gfx::Rect(200, 100)))));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
// RenderPassQuad is fully covered by quads.
viz::AreContentsOpaque(true)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
// Occluded by child pass.
viz::HasVisibleRect(gfx::Rect(100, 50)))));
}
// Move child pass to the top and move layer in pass to top half of pass.
child_pass_root->SetPosition(gfx::PointF(0.0f, 0.0f));
child_pass_layer->SetBounds(gfx::Size(200, 50));
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(200, 50)),
// Only top half is covered.
viz::HasVisibleRect(gfx::Rect(200, 50)))));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
// Pass rects shrinks to content rect.
viz::HasRect(gfx::Rect(100, 25)),
viz::HasVisibleRect(gfx::Rect(100, 25)),
viz::AreContentsOpaque(true)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
// Occluded by child pass.
viz::HasVisibleRect(gfx::Rect(0, 25, 100, 75)))));
}
// Add another layer so that the bottom right corner of the render pass is not
// covered.
auto child_pass_layer_2 =
CreateSolidColorLayer(gfx::Size(100, 50), SkColors::kGreen);
child_pass_layer_2->SetPosition(gfx::PointF(0.0f, 50.0f));
child_pass_root->AddChild(child_pass_layer_2);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
viz::HasRect(gfx::Rect(100, 50)),
viz::HasVisibleRect(gfx::Rect(100, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(200, 50)),
viz::HasVisibleRect(gfx::Rect(200, 50)))));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(100, 50)),
viz::HasVisibleRect(gfx::Rect(100, 50)),
// RenderPassQuad is fully covered.
viz::AreContentsOpaque(false)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
// Occluded by child pass.
viz::HasVisibleRect(gfx::Rect(0, 25, 100, 75)))));
}
// Add another layer to fully cover the child pass.
auto child_pass_layer_3 =
CreateSolidColorLayer(gfx::Size(100, 50), SkColors::kBlue);
child_pass_layer_3->SetPosition(gfx::PointF(100.0f, 50.0f));
child_pass_root->AddChild(child_pass_layer_3);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(100, 50)),
viz::HasVisibleRect(gfx::Rect(100, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
viz::HasRect(gfx::Rect(100, 50)),
viz::HasVisibleRect(gfx::Rect(100, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(200, 50)),
viz::HasVisibleRect(gfx::Rect(200, 50)))));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(100, 50)),
viz::HasVisibleRect(gfx::Rect(100, 50)),
// RenderPassQuad is fully covered.
viz::AreContentsOpaque(true)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
// Occluded by child pass.
viz::HasVisibleRect(gfx::Rect(0, 50, 100, 50)))));
}
// Expand child layer so it's partially occluded by child layer 2 and 3.
child_pass_layer->SetBounds(gfx::Size(200, 100));
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(100, 50)),
viz::HasVisibleRect(gfx::Rect(100, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
viz::HasRect(gfx::Rect(100, 50)),
viz::HasVisibleRect(gfx::Rect(100, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(200, 100)),
// Partially occluded.
viz::HasVisibleRect(gfx::Rect(200, 50)))));
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
// RenderPassQuad is fully covered.
viz::AreContentsOpaque(true)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
// Occluded by child pass.
viz::HasVisibleRect(gfx::Rect(0, 50, 100, 50)))));
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, OccludedNonOpaqueBackgroundColor) {
// Check that even if background color is not opaque, the frame should still
// be opaque if the viewport is entirely occluded by opaque layers.
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->set_background_color(SkColors::kTransparent);
layer_tree_->SetRoot(root_layer);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(pass->quad_list,
ElementsAre(viz::IsSolidColorQuad(SkColors::kGray)));
EXPECT_FALSE(pass->has_transparent_background);
}
TEST_F(SlimLayerTreeCompositorFrameTest, Guttering) {
auto root_layer = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
root_layer->SetPosition(gfx::PointF(25.0f, 25.0f));
layer_tree_->SetRoot(root_layer);
layer_tree_->set_background_color(SkColors::kBlue);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.front();
EXPECT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50))),
// Should require 4 gutter quads.
viz::IsSolidColorQuad(SkColors::kBlue),
viz::IsSolidColorQuad(SkColors::kBlue),
viz::IsSolidColorQuad(SkColors::kBlue),
viz::IsSolidColorQuad(SkColors::kBlue)));
EXPECT_FALSE(pass->has_transparent_background);
Region region;
for (auto& quad : frame.render_pass_list) {
region.Union(quad->output_rect);
}
EXPECT_TRUE(region.Contains(viewport_));
}
TEST_F(SlimLayerTreeCompositorFrameTest, PropertyDamage) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto solid_color_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
root_layer->AddChild(solid_color_layer);
auto check_frame = [&](SkColor4f color, gfx::Rect damage) {
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
EXPECT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(color),
viz::HasRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
EXPECT_EQ(pass->damage_rect, damage);
};
// First frame should have full damage.
check_frame(SkColors::kRed, viewport_);
solid_color_layer->SetBackgroundColor(SkColors::kGreen);
// Damage only the layer.
check_frame(SkColors::kGreen, gfx::Rect(50, 50));
solid_color_layer->SetPosition(gfx::PointF(10.2f, 10.2f));
// Damage newly exposed area as well. Also damage is rounded to enclosing
// rect.
check_frame(SkColors::kGreen, gfx::Rect(61, 61));
// Damage is empty if there is no change.
check_frame(SkColors::kGreen, gfx::Rect());
}
TEST_F(SlimLayerTreeCompositorFrameTest, PropertyChangeFromParentDamage) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto parent = Layer::Create();
auto solid_color_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
parent->AddChild(solid_color_layer);
root_layer->AddChild(parent);
auto check_frame = [&](gfx::Rect damage) {
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
EXPECT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
EXPECT_EQ(pass->damage_rect, damage);
};
// First frame should have full damage.
check_frame(viewport_);
parent->SetPosition(gfx::PointF(10.0f, 10.0f));
// Damage newly exposed area as well.
check_frame(gfx::Rect(60, 60));
parent->SetOpacity(0.5f);
check_frame(gfx::Rect(10, 10, 50, 50));
// Rotate about center, which does not change visible rect.
parent->SetTransformOrigin(gfx::Point3F(25.0f, 25.0f, 0.0f));
parent->SetTransform(gfx::Transform::Make90degRotation());
check_frame(gfx::Rect(10, 10, 50, 50));
// Damage is empty if there is no change.
check_frame(gfx::Rect());
solid_color_layer->RemoveFromParent();
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
EXPECT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
// Removed layer damages exposed area.
EXPECT_EQ(pass->damage_rect, gfx::Rect(10, 10, 50, 50));
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, NonRootPassDamage) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto parent = Layer::Create();
auto solid_color_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
parent->AddChild(solid_color_layer);
root_layer->AddChild(parent);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
EXPECT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
// First frame should have full damage.
EXPECT_EQ(pass->damage_rect, viewport_);
}
parent->SetFilters({cc::slim::Filter::CreateBrightness(0.5f)});
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
EXPECT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50)))));
EXPECT_EQ(child_pass->damage_rect, gfx::Rect(50, 50));
auto& root_pass = frame.render_pass_list.back();
EXPECT_THAT(
root_pass->quad_list,
ElementsAre(
AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
EXPECT_EQ(root_pass->damage_rect, gfx::Rect(50, 50));
}
// new frame with no change should not have damage.
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
EXPECT_EQ(child_pass->damage_rect, gfx::Rect());
auto& root_pass = frame.render_pass_list.back();
EXPECT_EQ(root_pass->damage_rect, gfx::Rect());
}
// Changing child layer damages both passes.
solid_color_layer->SetBackgroundColor(SkColors::kBlue);
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
EXPECT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50)))));
EXPECT_EQ(child_pass->damage_rect, gfx::Rect(50, 50));
auto& root_pass = frame.render_pass_list.back();
EXPECT_THAT(
root_pass->quad_list,
ElementsAre(
AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
EXPECT_EQ(root_pass->damage_rect, gfx::Rect(50, 50));
}
// Moving child pass damages root pass.
parent->SetPosition(gfx::PointF(25.0f, 25.0f));
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
EXPECT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50)))));
// Child pass damage rect ideally can be empty here because none of the
// layers inside the pass changed in relation to the pass. Current
// implementation uses Layer::NotifySubtreeChanged that damages the whole
// subtree across render passes which is why the child pass is damaged.
// Getting damage correct may be tricky and brittle, and currently viz
// ignores damage on non-root render passes, so this case is not
// implemented.
EXPECT_EQ(child_pass->damage_rect, gfx::Rect(50, 50));
auto& root_pass = frame.render_pass_list.back();
EXPECT_THAT(
root_pass->quad_list,
ElementsAre(
AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
EXPECT_EQ(root_pass->damage_rect, gfx::Rect(75, 75));
}
// Adding a layer outside the child pass and check child pass is not damaged.
root_layer->AddChild(
CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kGreen));
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
EXPECT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50)))));
EXPECT_EQ(child_pass->damage_rect, gfx::Rect());
auto& root_pass = frame.render_pass_list.back();
EXPECT_THAT(
root_pass->quad_list,
ElementsAre(
AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
viz::HasRect(gfx::Rect(10, 10)),
viz::HasVisibleRect(gfx::Rect(10, 10))),
AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(50, 50))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
EXPECT_EQ(root_pass->damage_rect, gfx::Rect(10, 10));
}
// Removing child pass damages parent pass.
solid_color_layer->RemoveFromParent();
{
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
EXPECT_THAT(pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
viz::HasRect(gfx::Rect(10, 10)),
viz::HasVisibleRect(gfx::Rect(10, 10))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
EXPECT_EQ(pass->damage_rect, gfx::Rect(25, 25, 50, 50));
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, SimpleRoundedCorner) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto solid_color_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
solid_color_layer->SetRoundedCorner(gfx::RoundedCornersF(20.0f));
solid_color_layer->SetPosition(gfx::PointF(10.0f, 10.0f));
root_layer->AddChild(solid_color_layer);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(
pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasTransform(
gfx::Transform::MakeTranslation(10.0f, 10.0f))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
auto* quad = pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasRoundedCorners());
EXPECT_TRUE(shared_quad_state->is_fast_rounded_corner);
EXPECT_EQ(shared_quad_state->mask_filter_info.rounded_corner_bounds(),
gfx::RRectF(10.0f, 10.0f, 50.0f, 50.0f, 20.0f));
}
TEST_F(SlimLayerTreeCompositorFrameTest, RoundedCornerWithChild) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto rounded_corner_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
rounded_corner_layer->SetRoundedCorner(gfx::RoundedCornersF(20.0f));
rounded_corner_layer->SetPosition(gfx::PointF(10.0f, 10.0f));
root_layer->AddChild(rounded_corner_layer);
auto child = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kBlue);
child->SetPosition(gfx::PointF(10.0f, 10.0f));
rounded_corner_layer->AddChild(child);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(
pass->quad_list,
ElementsAre(
AllOf(
viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(40, 40)),
viz::HasTransform(gfx::Transform::MakeTranslation(20.0f, 20.0f))),
AllOf(
viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasTransform(gfx::Transform::MakeTranslation(10.0f, 10.0f))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
const gfx::RRectF expected_rounded_conrer_in_target(10.0f, 10.0f, 50.0f,
50.0f, 20.0f);
{
auto* quad = pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasRoundedCorners());
EXPECT_TRUE(shared_quad_state->is_fast_rounded_corner);
EXPECT_EQ(shared_quad_state->mask_filter_info.rounded_corner_bounds(),
expected_rounded_conrer_in_target);
}
{
auto* quad = pass->quad_list.ElementAt(1u);
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasRoundedCorners());
EXPECT_TRUE(shared_quad_state->is_fast_rounded_corner);
EXPECT_EQ(shared_quad_state->mask_filter_info.rounded_corner_bounds(),
expected_rounded_conrer_in_target);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, NonAxisAlignedRoundedCorner) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto rounded_corner_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
rounded_corner_layer->SetRoundedCorner(gfx::RoundedCornersF(20.0f));
rounded_corner_layer->SetTransformOrigin(gfx::Point3F(25.0f, 25.0f, 0.0f));
gfx::Transform transform;
transform.Rotate(45);
rounded_corner_layer->SetTransform(transform);
root_layer->AddChild(rounded_corner_layer);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(50, 50)),
viz::HasTransform(gfx::Transform()))));
{
auto* quad = child_pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasRoundedCorners());
EXPECT_TRUE(shared_quad_state->is_fast_rounded_corner);
EXPECT_EQ(shared_quad_state->mask_filter_info.rounded_corner_bounds(),
gfx::RRectF(0.0f, 0.0f, 50.0f, 50.0f, 20.0f));
}
auto& root_pass = frame.render_pass_list.back();
gfx::Transform child_pass_transform =
gfx::Transform::MakeTranslation(25.0f, 25.0f);
child_pass_transform.PreConcat(transform);
child_pass_transform.PreConcat(
gfx::Transform::MakeTranslation(-25.0f, -25.0f));
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(AllOf(viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasTransform(child_pass_transform)),
AllOf(viz::IsSolidColorQuad(SkColors::kGray),
viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
{
auto* quad = root_pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_FALSE(shared_quad_state->mask_filter_info.HasRoundedCorners());
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, RoundedCornerOnParentAndChild) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
auto parent = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
parent->SetRoundedCorner(gfx::RoundedCornersF(20.0f));
parent->SetPosition(gfx::PointF(10.0f, 10.0f));
root_layer->AddChild(parent);
auto child = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kBlue);
child->SetPosition(gfx::PointF(10.0f, 10.0f));
child->SetRoundedCorner(gfx::RoundedCornersF(15.0f));
parent->AddChild(child);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(40, 40)),
viz::HasTransform(gfx::Transform()))));
{
auto* quad = child_pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasRoundedCorners());
EXPECT_TRUE(shared_quad_state->is_fast_rounded_corner);
EXPECT_EQ(shared_quad_state->mask_filter_info.rounded_corner_bounds(),
gfx::RRectF(0.0f, 0.0f, 50.0f, 50.0f, 15.0f));
}
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(
AllOf(
viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(40, 40)),
viz::HasVisibleRect(gfx::Rect(40, 40)),
viz::HasTransform(gfx::Transform::MakeTranslation(20.0f, 20.0f))),
AllOf(
viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasTransform(gfx::Transform::MakeTranslation(10.0f, 10.0f))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
const gfx::RRectF expected_rounded_conrer_in_target(10.0f, 10.0f, 50.0f,
50.0f, 20.0f);
{
auto* quad = root_pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasRoundedCorners());
EXPECT_TRUE(shared_quad_state->is_fast_rounded_corner);
EXPECT_EQ(shared_quad_state->mask_filter_info.rounded_corner_bounds(),
expected_rounded_conrer_in_target);
}
{
auto* quad = root_pass->quad_list.ElementAt(1u);
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasRoundedCorners());
EXPECT_TRUE(shared_quad_state->is_fast_rounded_corner);
EXPECT_EQ(shared_quad_state->mask_filter_info.rounded_corner_bounds(),
expected_rounded_conrer_in_target);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, GradientMaskWithChild) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
gfx::LinearGradient gradient;
gradient.AddStep(0.0f, 255);
gradient.AddStep(1.0f, 0);
auto gradient_layer =
CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
gradient_layer->SetGradientMask(gradient);
gradient_layer->SetPosition(gfx::PointF(10.0f, 10.0f));
root_layer->AddChild(gradient_layer);
auto child = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kBlue);
child->SetPosition(gfx::PointF(10.0f, 10.0f));
gradient_layer->AddChild(child);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 1u);
auto& pass = frame.render_pass_list.back();
ASSERT_THAT(
pass->quad_list,
ElementsAre(
AllOf(
viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(40, 40)),
viz::HasTransform(gfx::Transform::MakeTranslation(20.0f, 20.0f))),
AllOf(
viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasTransform(gfx::Transform::MakeTranslation(10.0f, 10.0f))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
{
auto* quad = pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasGradientMask());
EXPECT_EQ(shared_quad_state->mask_filter_info.gradient_mask(), gradient);
}
{
auto* quad = pass->quad_list.ElementAt(1u);
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasGradientMask());
EXPECT_EQ(shared_quad_state->mask_filter_info.gradient_mask(), gradient);
}
}
TEST_F(SlimLayerTreeCompositorFrameTest, GradientMaskOnParentAndChild) {
auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
layer_tree_->SetRoot(root_layer);
gfx::LinearGradient parent_gradient;
parent_gradient.AddStep(0.0f, 255);
parent_gradient.AddStep(1.0f, 0);
auto parent = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kRed);
parent->SetGradientMask(parent_gradient);
parent->SetPosition(gfx::PointF(10.0f, 10.0f));
root_layer->AddChild(parent);
gfx::LinearGradient child_gradient;
child_gradient.AddStep(0.0f, 0);
child_gradient.AddStep(1.0f, 255);
auto child = CreateSolidColorLayer(gfx::Size(50, 50), SkColors::kBlue);
child->SetPosition(gfx::PointF(10.0f, 10.0f));
child->SetGradientMask(child_gradient);
parent->AddChild(child);
viz::CompositorFrame frame = ProduceFrame();
ASSERT_EQ(frame.render_pass_list.size(), 2u);
auto& child_pass = frame.render_pass_list.front();
ASSERT_THAT(child_pass->quad_list,
ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kBlue),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasVisibleRect(gfx::Rect(40, 40)),
viz::HasTransform(gfx::Transform()))));
{
auto* quad = child_pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasGradientMask());
EXPECT_EQ(shared_quad_state->mask_filter_info.gradient_mask(),
child_gradient);
}
auto& root_pass = frame.render_pass_list.back();
ASSERT_THAT(
root_pass->quad_list,
ElementsAre(
AllOf(
viz::IsCompositorRenderPassQuad(child_pass->id),
viz::HasRect(gfx::Rect(40, 40)),
viz::HasVisibleRect(gfx::Rect(40, 40)),
viz::HasTransform(gfx::Transform::MakeTranslation(20.0f, 20.0f))),
AllOf(
viz::IsSolidColorQuad(SkColors::kRed),
viz::HasRect(gfx::Rect(50, 50)),
viz::HasTransform(gfx::Transform::MakeTranslation(10.0f, 10.0f))),
AllOf(viz::IsSolidColorQuad(SkColors::kGray), viz::HasRect(viewport_),
viz::HasVisibleRect(viewport_),
viz::HasTransform(gfx::Transform()))));
{
auto* quad = root_pass->quad_list.front();
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasGradientMask());
EXPECT_EQ(shared_quad_state->mask_filter_info.gradient_mask(),
parent_gradient);
}
{
auto* quad = root_pass->quad_list.ElementAt(1u);
auto* shared_quad_state = quad->shared_quad_state;
EXPECT_TRUE(shared_quad_state->mask_filter_info.HasGradientMask());
EXPECT_EQ(shared_quad_state->mask_filter_info.gradient_mask(),
parent_gradient);
}
}
// Testing that {Add|Remove}SurfaceRange should trigger a draw via
// `SetNeedsDraw`, where the added or removed surface range should be reflected
// in the metadata of the next frame's metadata.
TEST_F(SlimLayerTreeCompositorFrameTest,
AddRemoveSurfaceRangesTriggerSetNeedsDraw) {
auto surface_layer = SurfaceLayer::Create();
surface_layer->SetBounds(viewport_.size());
surface_layer->SetIsDrawable(true);
surface_layer->SetContentsOpaque(true);
layer_tree_->SetRoot(surface_layer);
base::UnguessableToken token = base::UnguessableToken::Create();
viz::SurfaceId start(viz::FrameSinkId(1u, 2u),
viz::LocalSurfaceId(3u, 4u, token));
viz::SurfaceId end(viz::FrameSinkId(1u, 2u),
viz::LocalSurfaceId(5u, 6u, token));
cc::DeadlinePolicy deadline_policy = cc::DeadlinePolicy::UseDefaultDeadline();
surface_layer->SetOldestAcceptableFallback(start);
surface_layer->SetSurfaceId(end, deadline_policy);
// Add/remove a SurfaceRange different from the one of the `surface_layer`.
{
layer_tree_->AddSurfaceRange(viz::SurfaceRange(end, end));
const viz::CompositorFrame frame = ProduceFrame();
EXPECT_THAT(frame.metadata.referenced_surfaces,
testing::UnorderedElementsAre(viz::SurfaceRange(start, end),
viz::SurfaceRange(end, end)));
}
{
layer_tree_->RemoveSurfaceRange(viz::SurfaceRange(end, end));
const viz::CompositorFrame frame = ProduceFrame();
EXPECT_THAT(frame.metadata.referenced_surfaces,
testing::UnorderedElementsAre(viz::SurfaceRange(start, end)));
}
// Add/remove a SurfaceRange that's the same as the one of the
// `surface_layer`. Since the ranges are the same, only one range entry is
// referenced in the metadata.
{
layer_tree_->AddSurfaceRange(viz::SurfaceRange(start, end));
const viz::CompositorFrame frame = ProduceFrame();
EXPECT_THAT(frame.metadata.referenced_surfaces,
testing::UnorderedElementsAre(viz::SurfaceRange(start, end)));
}
{
layer_tree_->RemoveSurfaceRange(viz::SurfaceRange(start, end));
const viz::CompositorFrame frame = ProduceFrame();
EXPECT_THAT(frame.metadata.referenced_surfaces,
testing::UnorderedElementsAre(viz::SurfaceRange(start, end)));
}
}
} // namespace
} // namespace cc::slim