blob: 39e0437492e3a7f532cd1b75c0b08a80ce23c285 [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/display/overlay_candidate_factory.h"
#include <unordered_map>
#include <vector>
#include "base/dcheck_is_on.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/gpu/context_provider.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/draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "components/viz/service/display/aggregated_frame.h"
#include "components/viz/service/display/display_resource_provider.h"
#include "components/viz/service/display/display_resource_provider_null.h"
#include "components/viz/service/display/overlay_candidate.h"
#include "components/viz/test/test_context_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/test/geometry_util.h"
#include "ui/gfx/overlay_transform_utils.h"
#include "ui/gfx/video_types.h"
using testing::_;
using testing::Mock;
namespace viz {
namespace {
using RoundedDisplayMasksInfo = TextureDrawQuad::RoundedDisplayMasksInfo;
constexpr OverlayCandidateFactory::OverlayContext kOverlayContextForTesting{
.is_delegated_context = true,
.supports_clip_rect = true,
.supports_arbitrary_transform = true,
.supports_rounded_display_masks = false};
// TODO(zoraiznaeem): Move resource creation code into OverlayTestBase class.
class OverlayCandidateFactoryTestBase : public testing::Test {
public:
OverlayCandidateFactoryTestBase() = default;
OverlayCandidateFactoryTestBase(const OverlayCandidateFactoryTestBase&) =
delete;
OverlayCandidateFactoryTestBase& operator=(
const OverlayCandidateFactoryTestBase&) = delete;
~OverlayCandidateFactoryTestBase() override = default;
protected:
void TearDown() override {
child_resource_provider_.ReleaseAllExportedResources(true);
}
ResourceId CreateResource(bool is_overlay_candidate) {
scoped_refptr<ContextProvider> child_context_provider =
TestContextProvider::Create();
child_context_provider->BindToCurrentSequence();
auto resource = TransferableResource::MakeGpu(
gpu::Mailbox::GenerateForSharedImage(), GL_TEXTURE_2D, gpu::SyncToken(),
gfx::Size(1, 1), SinglePlaneFormat::kRGBA_8888, is_overlay_candidate);
ResourceId resource_id =
child_resource_provider_.ImportResource(resource, base::DoNothing());
int child_id =
resource_provider_.CreateChild(base::DoNothing(), SurfaceId());
// Transfer resource to the parent.
std::vector<ResourceId> resource_ids_to_transfer;
resource_ids_to_transfer.push_back(resource_id);
std::vector<TransferableResource> list;
child_resource_provider_.PrepareSendToParent(
resource_ids_to_transfer, &list, child_context_provider.get());
resource_provider_.ReceiveFromChild(child_id, list);
// Delete it in the child so it won't be leaked, and will be released once
// returned from the parent.
child_resource_provider_.RemoveImportedResource(resource_id);
// In DisplayResourceProvider's namespace, use the mapped resource id.
std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
resource_provider_.GetChildToParentMap(child_id);
return resource_map[list[0].id];
}
OverlayCandidateFactory CreateCandidateFactory(
const AggregatedRenderPass& render_pass,
const gfx::RectF& primary_rect,
const OverlayCandidateFactory::OverlayContext& context) {
return OverlayCandidateFactory(
&render_pass, &resource_provider_, &surface_damage_list_, &identity_,
primary_rect, &render_pass_filters_, context);
}
ClientResourceProvider child_resource_provider_;
DisplayResourceProviderNull resource_provider_;
SurfaceDamageRectList surface_damage_list_;
SkM44 identity_;
OverlayProcessorInterface::FilterOperationsMap render_pass_filters_;
};
SolidColorDrawQuad* AddQuad(const gfx::Rect quad_rect,
const gfx::Transform& quad_to_target_transform,
AggregatedRenderPass* render_pass,
const absl::optional<gfx::Rect> clip_rect,
const gfx::Rect visible_rect) {
SharedQuadState* quad_state = render_pass->CreateAndAppendSharedQuadState();
quad_state->SetAll(
/*transform=*/quad_to_target_transform, quad_rect,
/*visible_layer_rect=*/quad_rect,
/*filter_info=*/gfx::MaskFilterInfo(), clip_rect,
/*are contents opaque=*/true,
/*opacity_f=*/1.f,
/*blend=*/SkBlendMode::kSrcOver, /*sorting_context=*/0);
SolidColorDrawQuad* solid_quad =
render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
solid_quad->SetNew(quad_state, quad_rect, visible_rect, SkColors::kBlack,
false /* force_anti_aliasing_off */);
return solid_quad;
}
SolidColorDrawQuad* AddQuad(const gfx::Rect quad_rect,
const gfx::Transform& quad_to_target_transform,
AggregatedRenderPass* render_pass) {
return AddQuad(quad_rect, quad_to_target_transform, render_pass,
absl::nullopt, quad_rect);
}
AggregatedRenderPassDrawQuad* AddRenderPassQuad(
gfx::Rect quad_rect,
gfx::Transform transform,
absl::optional<gfx::Rect> clip_rect,
AggregatedRenderPassId rpid,
AggregatedRenderPass* render_pass) {
SharedQuadState* quad_state = render_pass->CreateAndAppendSharedQuadState();
quad_state->SetAll(
/*transform=*/transform, quad_rect,
/*visible_layer_rect=*/quad_rect,
/*filter_info=*/gfx::MaskFilterInfo(),
/*clip=*/clip_rect,
/*are contents opaque=*/true,
/*opacity_f=*/1.f,
/*blend=*/SkBlendMode::kSrcOver, /*sorting_context=*/0);
auto* rpdq =
render_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
rpdq->SetNew(quad_state, quad_rect, quad_rect, rpid, kInvalidResourceId,
gfx::RectF(), gfx::Size(), gfx::Vector2dF(1, 1), gfx::PointF(),
gfx::RectF(), false, 1.0f);
return rpdq;
}
OverlayCandidate CreateCandidate(float left,
float top,
float right,
float bottom) {
OverlayCandidate candidate;
candidate.display_rect.SetRect(left, top, right - left, bottom - top);
return candidate;
}
using OverlayCandidateFactoryTest = OverlayCandidateFactoryTestBase;
TEST_F(OverlayCandidateFactoryTest, IsOccluded) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 1, 1), gfx::Rect(), gfx::Transform());
OverlayCandidateFactory factory =
CreateCandidateFactory(render_pass, gfx::RectF(render_pass.output_rect),
kOverlayContextForTesting);
gfx::Transform identity;
identity.MakeIdentity();
// Create overlapping quads around 1,1 - 10,10.
AddQuad(gfx::Rect(0, 0, 1, 10), identity, &render_pass);
AddQuad(gfx::Rect(0, 0, 10, 1), identity, &render_pass);
AddQuad(gfx::Rect(10, 0, 1, 10), identity, &render_pass);
AddQuad(gfx::Rect(0, 10, 10, 1), identity, &render_pass);
EXPECT_FALSE(factory.IsOccluded(CreateCandidate(0.5f, 0.5f, 10.49f, 10.49f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
EXPECT_TRUE(factory.IsOccluded(CreateCandidate(0.49f, 0.5f, 10.49f, 10.49f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
EXPECT_TRUE(factory.IsOccluded(CreateCandidate(0.5f, 0.49f, 10.50f, 10.5f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
EXPECT_TRUE(factory.IsOccluded(CreateCandidate(0.5f, 0.5f, 10.5f, 10.49f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
EXPECT_TRUE(factory.IsOccluded(CreateCandidate(0.5f, 0.5f, 10.49f, 10.5f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
}
TEST_F(OverlayCandidateFactoryTest, IsOccludedScaled) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 1, 1), gfx::Rect(), gfx::Transform());
OverlayCandidateFactory factory =
CreateCandidateFactory(render_pass, gfx::RectF(render_pass.output_rect),
kOverlayContextForTesting);
gfx::Transform quad_to_target_transform;
quad_to_target_transform.Scale(1.6, 1.6);
// Create overlapping quads around 1.6,2.4 - 14.4,17.6.
AddQuad(gfx::Rect(0, 0, 1, 10), quad_to_target_transform, &render_pass);
AddQuad(gfx::Rect(0, 0, 10, 2), quad_to_target_transform, &render_pass);
AddQuad(gfx::Rect(9, 0, 1, 10), quad_to_target_transform, &render_pass);
AddQuad(gfx::Rect(0, 11, 10, 1), quad_to_target_transform, &render_pass);
EXPECT_FALSE(factory.IsOccluded(CreateCandidate(2.f, 3.f, 14.f, 17.f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
EXPECT_TRUE(factory.IsOccluded(CreateCandidate(1.f, 3.f, 14.f, 17.f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
EXPECT_TRUE(factory.IsOccluded(CreateCandidate(2.f, 2.f, 14.f, 17.f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
EXPECT_TRUE(factory.IsOccluded(CreateCandidate(2.f, 3.f, 15.f, 17.f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
EXPECT_TRUE(factory.IsOccluded(CreateCandidate(2.f, 3.f, 15.f, 18.f),
render_pass.quad_list.begin(),
render_pass.quad_list.end()));
}
class OverlayCandidateFactoryArbitraryTransformTest
: public OverlayCandidateFactoryTestBase {
protected:
TextureDrawQuad CreateUnclippedDrawQuad(
AggregatedRenderPass& render_pass,
const gfx::Rect& quad_rect,
const gfx::Transform& quad_to_target_transform) {
SharedQuadState* sqs = render_pass.CreateAndAppendSharedQuadState();
sqs->quad_to_target_transform = quad_to_target_transform;
TextureDrawQuad quad;
float vertex_opacity[4] = {1.0, 1.0, 1.0, 1.0};
quad.SetNew(sqs, quad_rect, quad_rect, false,
CreateResource(/*is_overlay_candidate=*/true), false,
gfx::PointF(), gfx::PointF(1, 1), SkColors::kTransparent,
vertex_opacity, false, false, false,
gfx::ProtectedVideoType::kClear);
return quad;
}
};
// Check that even axis-aligned transforms are stored separately from the
// display rect.
TEST_F(OverlayCandidateFactoryArbitraryTransformTest,
AxisAlignedNotBakedIntoDisplayRect) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 1, 1), gfx::Rect(), gfx::Transform());
OverlayCandidateFactory factory =
CreateCandidateFactory(render_pass, gfx::RectF(render_pass.output_rect),
kOverlayContextForTesting);
gfx::Transform transform;
transform.Translate(1, 2);
transform.Scale(3, 4);
auto quad = CreateUnclippedDrawQuad(render_pass, gfx::Rect(1, 1), transform);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
EXPECT_EQ(absl::get<gfx::Transform>(candidate.transform), transform);
EXPECT_EQ(candidate.display_rect, gfx::RectF(0, 0, 1, 1));
}
// Check that even arbitrary transforms are preserved on the overlay
// candidate.
TEST_F(OverlayCandidateFactoryArbitraryTransformTest, SupportsNonAxisAligned) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 1, 1), gfx::Rect(), gfx::Transform());
OverlayCandidateFactory factory =
CreateCandidateFactory(render_pass, gfx::RectF(render_pass.output_rect),
kOverlayContextForTesting);
gfx::Transform transform;
transform.Rotate(1);
transform.Skew(2, 3);
auto quad = CreateUnclippedDrawQuad(render_pass, gfx::Rect(1, 1), transform);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
EXPECT_EQ(absl::get<gfx::Transform>(candidate.transform), transform);
EXPECT_EQ(candidate.display_rect, gfx::RectF(0, 0, 1, 1));
}
// Check that we include the Y-flip state with our arbitrary transform since
// we don't include it on the gfx::OverlayTransform in this case.
TEST_F(OverlayCandidateFactoryArbitraryTransformTest, TransformIncludesYFlip) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 1, 1), gfx::Rect(), gfx::Transform());
OverlayCandidateFactory factory =
CreateCandidateFactory(render_pass, gfx::RectF(render_pass.output_rect),
kOverlayContextForTesting);
gfx::Transform transform;
auto quad = CreateUnclippedDrawQuad(render_pass, gfx::Rect(1, 1), transform);
quad.y_flipped = true;
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
gfx::Transform transform_y_flipped;
transform_y_flipped.Translate(0, 1);
transform_y_flipped.Scale(1, -1);
EXPECT_EQ(absl::get<gfx::Transform>(candidate.transform),
transform_y_flipped);
gfx::PointF display_rect_origin =
absl::get<gfx::Transform>(candidate.transform)
.MapPoint(candidate.display_rect.origin());
EXPECT_EQ(display_rect_origin, gfx::PointF(0, 1));
EXPECT_EQ(candidate.display_rect, gfx::RectF(0, 0, 1, 1));
}
#if DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST)
// Check that OverlayCandidateFactory isn't changed to allow for arbitrary
// transform support when clip support is not available. Such a configuration
// would likely be incorrect since clip rects are generally provided in target
// space and cannot be baked into the display rect when there is an arbitrary
// transform in between.
TEST_F(OverlayCandidateFactoryArbitraryTransformTest, DeathOnNoClipSupport) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 1, 1), gfx::Rect(), gfx::Transform());
const OverlayCandidateFactory::OverlayContext context = {
.is_delegated_context = true,
.supports_clip_rect = false,
.supports_arbitrary_transform = true};
EXPECT_DEATH(CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect), context),
"context_.supports_clip_rect \\|\\| "
"!context_.supports_arbitrary_transform");
}
// Resource-less overlays use the overlay quad in target space for damage
// calculation. This doesn't make sense with arbitrary transforms, so we expect
// a DCHECK to trip.
TEST_F(OverlayCandidateFactoryArbitraryTransformTest,
DeathOnResourcelessAndArbitraryTransform) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 2, 2), gfx::Rect(0, 0, 1, 1),
gfx::Transform());
OverlayCandidateFactory factory =
CreateCandidateFactory(render_pass, gfx::RectF(render_pass.output_rect),
kOverlayContextForTesting);
SharedQuadState* sqs = render_pass.CreateAndAppendSharedQuadState();
sqs->quad_to_target_transform.Rotate(1);
SolidColorDrawQuad quad;
quad.SetNew(sqs, gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1), SkColors::kRed,
true);
OverlayCandidate candidate;
EXPECT_DEATH(factory.FromDrawQuad(&quad, candidate),
"absl::holds_alternative<gfx::OverlayTransform>");
}
#endif
// Check that a factory fails to promote a quad with a non-axis-aligned
// transform when it doesn't support arbitrary transforms.
TEST_F(OverlayCandidateFactoryArbitraryTransformTest,
NoArbitraryTransformSupportFails) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 1, 1), gfx::Rect(), gfx::Transform());
const OverlayCandidateFactory::OverlayContext context{
.is_delegated_context = true,
.supports_clip_rect = true,
.supports_arbitrary_transform = false};
OverlayCandidateFactory factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect), context);
gfx::Transform transform;
transform.Rotate(1);
auto quad = CreateUnclippedDrawQuad(render_pass, gfx::Rect(1, 1), transform);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result,
OverlayCandidate::CandidateStatus::kFailNotAxisAligned2dRotation);
}
TEST_F(OverlayCandidateFactoryArbitraryTransformTest,
OccludedByFilteredQuadWorksInTargetSpace) {
AggregatedRenderPassId render_pass_id =
AggregatedRenderPassId::FromUnsafeValue(1);
AggregatedRenderPass render_pass;
render_pass.SetNew(render_pass_id, gfx::Rect(0, 0, 2, 2), gfx::Rect(),
gfx::Transform());
const OverlayCandidateFactory::OverlayContext context{
.is_delegated_context = true,
.supports_clip_rect = true,
.supports_arbitrary_transform = false};
OverlayCandidateFactory factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect), context);
QuadList quad_list;
AggregatedRenderPassDrawQuad* rpdq =
quad_list.AllocateAndConstruct<AggregatedRenderPassDrawQuad>();
rpdq->SetNew(render_pass.CreateAndAppendSharedQuadState(),
gfx::Rect(1, 1, 1, 1), gfx::Rect(1, 1, 1, 1), render_pass_id,
kInvalidResourceId, gfx::RectF(), gfx::Size(),
gfx::Vector2dF(1, 1), gfx::PointF(0, 0), gfx::RectF(), false,
1.0);
base::flat_map<AggregatedRenderPassId, cc::FilterOperations*> filter_map;
// The actual filter operation doesn't matter in this case.
cc::FilterOperations filter_op;
filter_map.insert({render_pass_id, &filter_op});
// Check that an untransformed 1x1 quad doesn't intersect with the filtered
// RPDQ.
{
gfx::Transform transform;
auto quad =
CreateUnclippedDrawQuad(render_pass, gfx::Rect(1, 1), transform);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
EXPECT_FALSE(factory.IsOccludedByFilteredQuad(candidate, quad_list.begin(),
quad_list.end(), filter_map));
}
// Check that a transformed 1x1 quad intersects with the filtered RPDQ.
{
gfx::Transform transform;
transform.Translate(0.5, 0.5);
auto quad =
CreateUnclippedDrawQuad(render_pass, gfx::Rect(1, 1), transform);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
EXPECT_TRUE(factory.IsOccludedByFilteredQuad(candidate, quad_list.begin(),
quad_list.end(), filter_map));
}
}
TEST_F(OverlayCandidateFactoryArbitraryTransformTest,
UnassignedDamageWithArbitraryTransforms) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 2, 2), gfx::Rect(), gfx::Transform());
// Add damage so that the factory has unassigned surface damage internally.
surface_damage_list_.emplace_back(1, 1, 1, 1);
OverlayCandidateFactory factory =
CreateCandidateFactory(render_pass, gfx::RectF(render_pass.output_rect),
kOverlayContextForTesting);
// Make a rotated quad which doesn't intersect with the damage, but the
// axis-aligned bounding box of its target space rect does. This rect should
// not get any damage.
{
gfx::Transform transform;
transform.Translate(0, -1);
transform.Rotate(-45);
auto quad =
CreateUnclippedDrawQuad(render_pass, gfx::Rect(2, 2), transform);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
EXPECT_TRUE(candidate.damage_rect.IsEmpty());
QuadList quad_list;
EXPECT_EQ(factory.EstimateVisibleDamage(&quad, candidate, quad_list.begin(),
quad_list.end()),
0);
}
// Ensure when that same rect does intersect with the damage picks up damage.
{
gfx::Transform transform;
transform.Rotate(-45);
auto quad =
CreateUnclippedDrawQuad(render_pass, gfx::Rect(2, 2), transform);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
// Damage should not be assigned to transformed quads.
EXPECT_TRUE(candidate.damage_rect.IsEmpty());
QuadList quad_list;
// But we can still estimate damage for sorting purposes.
EXPECT_GT(factory.EstimateVisibleDamage(&quad, candidate, quad_list.begin(),
quad_list.end()),
0);
}
}
constexpr float kEpsilon = 0.001f;
// Check that uv clips are applied correctly when the candidate is transformed.
class TransformedOverlayClipRectTest : public OverlayCandidateFactoryTestBase {
protected:
TextureDrawQuad CreateClippedDrawQuad(
AggregatedRenderPass& render_pass,
const gfx::Rect& quad_rect,
const gfx::Transform& quad_to_target_transform,
const gfx::Rect& clip_rect,
const gfx::RectF& quad_uv_rect) {
SharedQuadState* sqs = render_pass.CreateAndAppendSharedQuadState();
sqs->quad_to_target_transform = quad_to_target_transform;
sqs->clip_rect = clip_rect;
TextureDrawQuad quad;
float vertex_opacity[4] = {1.0, 1.0, 1.0, 1.0};
quad.SetNew(sqs, quad_rect, quad_rect, false,
CreateResource(/*is_overlay_candidate=*/true), false,
quad_uv_rect.origin(), quad_uv_rect.bottom_right(),
SkColors::kTransparent, vertex_opacity, false, false, false,
gfx::ProtectedVideoType::kClear);
return quad;
}
void RunClipToTopLeftCornerTest(gfx::OverlayTransform overlay_transform,
gfx::RectF quad_uvs,
gfx::RectF expected_uvs) {
AggregatedRenderPass render_pass;
gfx::Rect bounds(100, 100);
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1), bounds,
gfx::Rect(), gfx::Transform());
// Create a factory without clip rect or arbitrary transform delegation, so
// that any clips will be baked into the candidate.
OverlayCandidateFactory factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true});
// |transform| maps the rect (0,0 1x1) to (50,50 100x100).
gfx::Transform transform =
gfx::OverlayTransformToTransform(overlay_transform, gfx::SizeF(1, 1));
transform.PostScale(100, 100);
transform.PostTranslate(50, 50);
auto quad = CreateClippedDrawQuad(render_pass, gfx::Rect(1, 1), transform,
bounds, quad_uvs);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(&quad, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
EXPECT_EQ(absl::get<gfx::OverlayTransform>(candidate.transform),
overlay_transform);
EXPECT_EQ(candidate.display_rect, gfx::RectF(50, 50, 50, 50));
EXPECT_TRUE(
candidate.uv_rect.ApproximatelyEqual(expected_uvs, kEpsilon, kEpsilon));
}
};
TEST_F(TransformedOverlayClipRectTest, NoTransform) {
RunClipToTopLeftCornerTest(gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE,
gfx::RectF(1, 1),
gfx::RectF(0.0f, 0.0f, 0.5f, 0.5f));
}
TEST_F(TransformedOverlayClipRectTest, Rotate90) {
// If the candidate is rotated by 90 degrees, the top-left corner of the quad
// corresponds to the bottom-left corner in UV space.
RunClipToTopLeftCornerTest(gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_90,
gfx::RectF(1, 1),
gfx::RectF(0.0f, 0.5f, 0.5f, 0.5f));
}
TEST_F(TransformedOverlayClipRectTest, Rotate180) {
// If the candidate is rotated by 180 degrees, the top-left corner of the quad
// corresponds to the bottom-right corner in UV space.
RunClipToTopLeftCornerTest(
gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_180, gfx::RectF(1, 1),
gfx::RectF(0.5f, 0.5f, 0.5f, 0.5f));
}
TEST_F(TransformedOverlayClipRectTest, Rotate270) {
// If the candidate is rotated by 270 degrees, the top-left corner of the quad
// corresponds to the top-right corner in UV space.
RunClipToTopLeftCornerTest(
gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_270, gfx::RectF(1, 1),
gfx::RectF(0.5f, 0.0f, 0.5f, 0.5f));
}
TEST_F(TransformedOverlayClipRectTest, ClippedUvs) {
// Check that the clip is calculated correctly if the candidate's |uv_rect| is
// not full size, and offset from the origin.
RunClipToTopLeftCornerTest(
gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_180,
gfx::RectF(0.1f, 0.2f, 0.4f, 0.4f), gfx::RectF(0.3f, 0.4f, 0.2f, 0.2f));
}
TEST_F(OverlayCandidateFactoryTest, RenderPassClipped) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 100, 100), gfx::Rect(), gfx::Transform());
OverlayCandidateFactory factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true});
// Entirely clipped
gfx::Rect clip_rect(0, 0);
AggregatedRenderPassId rpid(2);
auto* rpdq = AddRenderPassQuad(gfx::Rect(100, 100), gfx::Transform(),
clip_rect, rpid, &render_pass);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(rpdq, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kFailVisible);
}
TEST_F(OverlayCandidateFactoryTest, RenderPassOffscreen) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 100, 100), gfx::Rect(), gfx::Transform());
OverlayCandidateFactory factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true});
AggregatedRenderPassId rpid(2);
gfx::Transform transform;
transform.Translate(gfx::Vector2dF(0, 101));
auto* rpdq = AddRenderPassQuad(gfx::Rect(100, 100), transform, absl::nullopt,
rpid, &render_pass);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(rpdq, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kFailVisible);
}
TEST_F(OverlayCandidateFactoryTest, RenderPassOffscreenBeforeFilter) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 100, 100), gfx::Rect(), gfx::Transform());
// Add a blur to this render pass that expands it's bounds into the viewport.
auto blur = cc::FilterOperation::CreateBlurFilter(10.0f);
cc::FilterOperations filter_ops;
filter_ops.Append(blur);
AggregatedRenderPassId rpid(2);
render_pass_filters_[rpid] = &filter_ops;
OverlayCandidateFactory factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true});
gfx::Transform transform;
transform.Translate(gfx::Vector2dF(0, 101));
auto* rpdq = AddRenderPassQuad(gfx::Rect(100, 100), transform, absl::nullopt,
rpid, &render_pass);
OverlayCandidate candidate;
OverlayCandidate::CandidateStatus result =
factory.FromDrawQuad(rpdq, candidate);
ASSERT_EQ(result, OverlayCandidate::CandidateStatus::kSuccess);
}
TEST_F(OverlayCandidateFactoryTest, ClipDelegation_Success) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 100, 100), gfx::Rect(), gfx::Transform());
gfx::Rect rect(0, 0, 75, 75);
gfx::Rect clip(0, 0, 50, 50);
gfx::Transform identity;
auto* quad = AddQuad(rect, identity, &render_pass, clip, rect);
OverlayCandidateFactory noclip_factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true});
OverlayCandidateFactory clip_factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true,
.supports_clip_rect = true});
OverlayCandidate no_clip_cand;
OverlayCandidate clip_cand;
ASSERT_EQ(noclip_factory.FromDrawQuad(quad, no_clip_cand),
OverlayCandidate::CandidateStatus::kSuccess);
ASSERT_EQ(clip_factory.FromDrawQuad(quad, clip_cand),
OverlayCandidate::CandidateStatus::kSuccess);
// Clip rect can be delegated if supported.
EXPECT_RECTF_EQ(no_clip_cand.display_rect, gfx::RectF(clip));
EXPECT_FALSE(no_clip_cand.clip_rect.has_value());
EXPECT_RECTF_EQ(clip_cand.display_rect, gfx::RectF(rect));
EXPECT_EQ(clip_cand.clip_rect.value(), clip);
}
TEST_F(OverlayCandidateFactoryTest, ClipDelegation_OutOfWindow) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 100, 100), gfx::Rect(), gfx::Transform());
gfx::Rect rect(0, 0, 75, 75);
gfx::Rect clip(0, 0, 50, 50);
// Transform up, outside the window.
gfx::Transform transform;
transform.Translate(gfx::Vector2dF(0, -30));
auto* quad = AddQuad(rect, transform, &render_pass, clip, rect);
OverlayCandidateFactory noclip_factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true});
OverlayCandidateFactory clip_factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true,
.supports_clip_rect = true});
OverlayCandidate no_clip_cand;
OverlayCandidate clip_cand;
ASSERT_EQ(noclip_factory.FromDrawQuad(quad, no_clip_cand),
OverlayCandidate::CandidateStatus::kSuccess);
ASSERT_EQ(clip_factory.FromDrawQuad(quad, clip_cand),
OverlayCandidate::CandidateStatus::kSuccess);
// Clip rect can't be delegated because it's no within the window.
gfx::RectF clipped(0, 0, 50, 45);
EXPECT_RECTF_EQ(no_clip_cand.display_rect, clipped);
EXPECT_FALSE(no_clip_cand.clip_rect.has_value());
EXPECT_RECTF_EQ(clip_cand.display_rect, clipped);
EXPECT_FALSE(clip_cand.clip_rect.has_value());
}
TEST_F(OverlayCandidateFactoryTest, ClipDelegation_VisibleRect) {
AggregatedRenderPass render_pass;
render_pass.SetNew(AggregatedRenderPassId::FromUnsafeValue(1),
gfx::Rect(0, 0, 100, 100), gfx::Rect(), gfx::Transform());
gfx::Rect rect(0, 0, 75, 75);
gfx::Rect clip(0, 0, 50, 50);
// Use content clipping.
gfx::Rect visible_rect = gfx::Rect(0, 10, 65, 75);
gfx::Transform identity;
auto* quad = AddQuad(rect, identity, &render_pass, clip, visible_rect);
OverlayCandidateFactory noclip_factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true});
OverlayCandidateFactory clip_factory = CreateCandidateFactory(
render_pass, gfx::RectF(render_pass.output_rect),
OverlayCandidateFactory::OverlayContext{.is_delegated_context = true,
.supports_clip_rect = true});
OverlayCandidate no_clip_cand;
OverlayCandidate clip_cand;
ASSERT_EQ(noclip_factory.FromDrawQuad(quad, no_clip_cand),
OverlayCandidate::CandidateStatus::kSuccess);
ASSERT_EQ(clip_factory.FromDrawQuad(quad, clip_cand),
OverlayCandidate::CandidateStatus::kSuccess);
// Clip rect can't be delegated because the quad has content clipping.
gfx::RectF clipped2(0, 10, 50, 40);
EXPECT_RECTF_EQ(no_clip_cand.display_rect, clipped2);
EXPECT_FALSE(no_clip_cand.clip_rect.has_value());
EXPECT_RECTF_EQ(clip_cand.display_rect, clipped2);
EXPECT_FALSE(clip_cand.clip_rect.has_value());
}
} // namespace
} // namespace viz