| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/flat_map.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/unguessable_token.h" |
| #include "cc/test/fake_output_surface_client.h" |
| #include "cc/test/resource_provider_test_utils.h" |
| #include "components/viz/client/client_resource_provider.h" |
| #include "components/viz/common/display/renderer_settings.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/solid_color_draw_quad.h" |
| #include "components/viz/common/quads/texture_draw_quad.h" |
| #include "components/viz/common/quads/video_hole_draw_quad.h" |
| #include "components/viz/common/quads/yuv_video_draw_quad.h" |
| #include "components/viz/common/resources/transferable_resource.h" |
| #include "components/viz/service/display/dc_layer_overlay.h" |
| #include "components/viz/service/display/display_resource_provider_skia.h" |
| #include "components/viz/service/display/output_surface.h" |
| #include "components/viz/service/display/output_surface_client.h" |
| #include "components/viz/service/display/output_surface_frame.h" |
| #include "components/viz/service/display/overlay_candidate.h" |
| #include "components/viz/service/display/overlay_processor_win.h" |
| #include "components/viz/test/fake_skia_output_surface.h" |
| #include "components/viz/test/test_context_provider.h" |
| #include "components/viz/test/test_gles2_interface.h" |
| #include "gpu/config/gpu_finch_features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/video_types.h" |
| #include "ui/gl/gl_switches.h" |
| #include "ui/latency/latency_info.h" |
| |
| using testing::_; |
| using testing::Mock; |
| |
| namespace viz { |
| namespace { |
| |
| const gfx::Rect kOverlayRect(0, 0, 256, 256); |
| const gfx::Rect kOverlayBottomRightRect(128, 128, 128, 128); |
| |
| class MockDCLayerOutputSurface : public FakeSkiaOutputSurface { |
| public: |
| static std::unique_ptr<MockDCLayerOutputSurface> Create() { |
| auto provider = TestContextProvider::Create(); |
| provider->BindToCurrentSequence(); |
| return std::make_unique<MockDCLayerOutputSurface>(std::move(provider)); |
| } |
| |
| explicit MockDCLayerOutputSurface(scoped_refptr<ContextProvider> provider) |
| : FakeSkiaOutputSurface(std::move(provider)) { |
| capabilities_.supports_dc_layers = true; |
| } |
| |
| // OutputSurface implementation. |
| MOCK_METHOD1(SetEnableDCLayers, void(bool)); |
| }; |
| |
| class DCTestOverlayProcessor : public OverlayProcessorWin { |
| public: |
| explicit DCTestOverlayProcessor(OutputSurface* output_surface) |
| : OverlayProcessorWin(output_surface, |
| &debug_settings_, |
| std::make_unique<DCLayerOverlayProcessor>( |
| /*allowed_yuv_overlay_count=*/1, |
| true)) {} |
| DebugRendererSettings debug_settings_; |
| }; |
| |
| std::unique_ptr<AggregatedRenderPass> CreateRenderPass() { |
| AggregatedRenderPassId render_pass_id{1}; |
| gfx::Rect output_rect(0, 0, 256, 256); |
| |
| auto pass = std::make_unique<AggregatedRenderPass>(); |
| pass->SetNew(render_pass_id, output_rect, output_rect, gfx::Transform()); |
| |
| SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState(); |
| shared_state->opacity = 1.f; |
| return pass; |
| } |
| |
| static ResourceId CreateResourceInLayerTree( |
| ClientResourceProvider* child_resource_provider, |
| const gfx::Size& size, |
| bool is_overlay_candidate) { |
| auto resource = TransferableResource::MakeGpu( |
| gpu::Mailbox::GenerateForSharedImage(), GL_TEXTURE_2D, gpu::SyncToken(), |
| size, SinglePlaneFormat::kRGBA_8888, is_overlay_candidate); |
| |
| ResourceId resource_id = |
| child_resource_provider->ImportResource(resource, base::DoNothing()); |
| |
| return resource_id; |
| } |
| |
| ResourceId CreateResource(DisplayResourceProvider* parent_resource_provider, |
| ClientResourceProvider* child_resource_provider, |
| ContextProvider* child_context_provider, |
| const gfx::Size& size, |
| bool is_overlay_candidate) { |
| ResourceId resource_id = CreateResourceInLayerTree( |
| child_resource_provider, size, is_overlay_candidate); |
| |
| int child_id = |
| parent_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); |
| parent_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 = |
| parent_resource_provider->GetChildToParentMap(child_id); |
| return resource_map[list[0].id]; |
| } |
| |
| SolidColorDrawQuad* CreateSolidColorQuadAt( |
| const SharedQuadState* shared_quad_state, |
| SkColor4f color, |
| AggregatedRenderPass* render_pass, |
| const gfx::Rect& rect) { |
| SolidColorDrawQuad* quad = |
| render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| quad->SetNew(shared_quad_state, rect, rect, color, false); |
| return quad; |
| } |
| |
| void CreateOpaqueQuadAt(DisplayResourceProvider* resource_provider, |
| const SharedQuadState* shared_quad_state, |
| AggregatedRenderPass* render_pass, |
| const gfx::Rect& rect, |
| SkColor4f color) { |
| DCHECK(color.isOpaque()); |
| auto* color_quad = render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| color_quad->SetNew(shared_quad_state, rect, rect, color, false); |
| } |
| |
| YUVVideoDrawQuad* CreateFullscreenCandidateYUVVideoQuad( |
| DisplayResourceProvider* parent_resource_provider, |
| ClientResourceProvider* child_resource_provider, |
| ContextProvider* child_context_provider, |
| const SharedQuadState* shared_quad_state, |
| AggregatedRenderPass* render_pass) { |
| bool needs_blending = false; |
| gfx::Rect rect = render_pass->output_rect; |
| gfx::Size resource_size_in_pixels = rect.size(); |
| bool is_overlay_candidate = true; |
| ResourceId resource_id = CreateResource( |
| parent_resource_provider, child_resource_provider, child_context_provider, |
| resource_size_in_pixels, is_overlay_candidate); |
| |
| auto* overlay_quad = render_pass->CreateAndAppendDrawQuad<YUVVideoDrawQuad>(); |
| overlay_quad->SetNew( |
| shared_quad_state, rect, rect, needs_blending, resource_size_in_pixels, |
| gfx::Rect(resource_size_in_pixels), gfx::Size(1, 1), resource_id, |
| resource_id, resource_id, resource_id, gfx::ColorSpace::CreateREC601(), 0, |
| 1.0, 8, gfx::ProtectedVideoType::kClear, absl::nullopt); |
| |
| return overlay_quad; |
| } |
| |
| AggregatedRenderPassDrawQuad* CreateRenderPassDrawQuadAt( |
| AggregatedRenderPass* render_pass, |
| const SharedQuadState* shared_quad_state, |
| const gfx::Rect& rect, |
| AggregatedRenderPassId render_pass_id) { |
| AggregatedRenderPassDrawQuad* quad = |
| render_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>(); |
| quad->SetNew(shared_quad_state, rect, rect, render_pass_id, ResourceId(2), |
| gfx::RectF(), gfx::Size(), gfx::Vector2dF(1, 1), gfx::PointF(), |
| gfx::RectF(), false, 1.f); |
| return quad; |
| } |
| |
| SkM44 GetIdentityColorMatrix() { |
| return SkM44(); |
| } |
| |
| class DCLayerOverlayTest : public testing::Test, |
| public testing::WithParamInterface<bool> { |
| public: |
| bool IsUsingDCompPresenter() const { return GetParam(); } |
| |
| static const char* GetParamName( |
| const testing::TestParamInfo<ParamType>& info) { |
| return info.param ? "DCompPresenter" : "DirectCompositionChildSurfaceWin"; |
| } |
| |
| protected: |
| DCLayerOverlayTest() { |
| std::vector<base::test::FeatureRef> enabled_features; |
| std::vector<base::test::FeatureRef> disabled_features; |
| |
| // With DisableVideoOverlayIfMoving, videos are required to be stable for a |
| // certain number of frames to be considered for overlay promotion. This |
| // complicates tests since it adds behavior dependent on the number of times |
| // |Process| is called. |
| disabled_features.push_back(features::kDisableVideoOverlayIfMoving); |
| |
| if (IsUsingDCompPresenter()) { |
| enabled_features.push_back(features::kDCompPresenter); |
| } else { |
| disabled_features.push_back(features::kDCompPresenter); |
| } |
| |
| feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| } |
| |
| void SetUp() override { |
| output_surface_ = MockDCLayerOutputSurface::Create(); |
| output_surface_->BindToClient(&output_surface_client_); |
| |
| resource_provider_ = std::make_unique<DisplayResourceProviderSkia>(); |
| lock_set_for_external_use_.emplace(resource_provider_.get(), |
| output_surface_.get()); |
| |
| child_provider_ = TestContextProvider::Create(); |
| child_provider_->BindToCurrentSequence(); |
| child_resource_provider_ = std::make_unique<ClientResourceProvider>(); |
| |
| overlay_processor_ = |
| std::make_unique<DCTestOverlayProcessor>(output_surface_.get()); |
| overlay_processor_->set_using_dc_layers_for_testing(true); |
| overlay_processor_->SetViewportSize(gfx::Size(256, 256)); |
| overlay_processor_ |
| ->set_frames_since_last_qualified_multi_overlays_for_testing(5); |
| EXPECT_TRUE(overlay_processor_->IsOverlaySupported()); |
| |
| if (IsUsingDCompPresenter()) { |
| output_surface_plane_ = |
| OverlayProcessorInterface::OutputSurfaceOverlayPlane(); |
| } |
| } |
| |
| void TearDown() override { |
| overlay_processor_ = nullptr; |
| child_resource_provider_->ShutdownAndReleaseAllResources(); |
| child_resource_provider_ = nullptr; |
| child_provider_ = nullptr; |
| lock_set_for_external_use_.reset(); |
| resource_provider_ = nullptr; |
| output_surface_ = nullptr; |
| } |
| |
| OverlayProcessorInterface::OutputSurfaceOverlayPlane* |
| GetOutputSurfacePlane() { |
| if (IsUsingDCompPresenter()) { |
| EXPECT_TRUE(output_surface_plane_.has_value()); |
| return &output_surface_plane_.value(); |
| } else { |
| EXPECT_FALSE(output_surface_plane_.has_value()); |
| return nullptr; |
| } |
| } |
| |
| void TestRenderPassRootTransform(bool is_overlay); |
| |
| base::test::ScopedFeatureList feature_list_; |
| std::unique_ptr<MockDCLayerOutputSurface> output_surface_; |
| absl::optional<OverlayProcessorInterface::OutputSurfaceOverlayPlane> |
| output_surface_plane_; |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| std::unique_ptr<DisplayResourceProviderSkia> resource_provider_; |
| absl::optional<DisplayResourceProviderSkia::LockSetForExternalUse> |
| lock_set_for_external_use_; |
| scoped_refptr<TestContextProvider> child_provider_; |
| std::unique_ptr<ClientResourceProvider> child_resource_provider_; |
| std::unique_ptr<OverlayProcessorWin> overlay_processor_; |
| gfx::Rect damage_rect_; |
| std::vector<gfx::Rect> content_bounds_; |
| }; |
| |
| TEST_P(DCLayerOverlayTest, DisableVideoOverlayIfMovingFeature) { |
| auto ProcessForOverlaysSingleVideoRectWithOffset = |
| [&](gfx::Vector2d video_rect_offset) { |
| auto pass = CreateRenderPass(); |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), |
| pass.get()); |
| video_quad->rect = gfx::Rect(0, 0, 10, 10) + video_rect_offset; |
| video_quad->visible_rect = gfx::Rect(0, 0, 10, 10) + video_rect_offset; |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap |
| render_pass_backdrop_filters; |
| |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, {}, |
| GetOutputSurfacePlane(), &dc_layer_list, &damage_rect_, |
| &content_bounds_); |
| |
| return dc_layer_list; |
| }; |
| |
| { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndDisableFeature( |
| features::kDisableVideoOverlayIfMoving); |
| EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset({0, 0}).size()); |
| EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset({1, 0}).size()); |
| } |
| { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature( |
| features::kDisableVideoOverlayIfMoving); |
| // We expect an overlay promotion after a couple frames of no movement |
| for (int i = 0; i < 10; i++) { |
| ProcessForOverlaysSingleVideoRectWithOffset({0, 0}).size(); |
| } |
| EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset({0, 0}).size()); |
| |
| // Since the overlay candidate moved, we expect no overlays |
| EXPECT_EQ(0U, ProcessForOverlaysSingleVideoRectWithOffset({1, 0}).size()); |
| |
| // After some number of frames with no movement, we expect an overlay again |
| for (int i = 0; i < 10; i++) { |
| ProcessForOverlaysSingleVideoRectWithOffset({1, 0}).size(); |
| } |
| EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset({1, 0}).size()); |
| } |
| } |
| |
| TEST_P(DCLayerOverlayTest, Occluded) { |
| { |
| auto pass = CreateRenderPass(); |
| SharedQuadState* first_shared_state = pass->shared_quad_state_list.back(); |
| first_shared_state->overlay_damage_index = 0; |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 3, 100, 100), SkColors::kWhite); |
| |
| SharedQuadState* second_shared_state = |
| pass->CreateAndAppendSharedQuadState(); |
| second_shared_state->overlay_damage_index = 1; |
| auto* first_video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| // Set the protected video flag will force the quad to use hw overlay |
| first_video_quad->protected_video_type = |
| gfx::ProtectedVideoType::kHardwareProtected; |
| |
| SharedQuadState* third_shared_state = |
| pass->CreateAndAppendSharedQuadState(); |
| third_shared_state->overlay_damage_index = 2; |
| auto* second_video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| // Set the protected video flag will force the quad to use hw overlay |
| second_video_quad->protected_video_type = |
| gfx::ProtectedVideoType::kHardwareProtected; |
| second_video_quad->rect.set_origin(gfx::Point(2, 2)); |
| second_video_quad->visible_rect.set_origin(gfx::Point(2, 2)); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(1, 1, 10, 10); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(1, 1, 10, 10), gfx::Rect(0, 0, 0, 0), gfx::Rect(0, 0, 0, 0)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| EXPECT_EQ(2U, dc_layer_list.size()); |
| EXPECT_EQ(-1, dc_layer_list.front().plane_z_order); |
| EXPECT_EQ(-2, dc_layer_list.back().plane_z_order); |
| // Entire underlay rect must be redrawn. |
| EXPECT_EQ(gfx::Rect(0, 0, 256, 256), damage_rect_); |
| } |
| { |
| auto pass = CreateRenderPass(); |
| SharedQuadState* first_shared_state = pass->shared_quad_state_list.back(); |
| first_shared_state->overlay_damage_index = 0; |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(3, 3, 100, 100), SkColors::kWhite); |
| |
| SharedQuadState* second_shared_state = |
| pass->CreateAndAppendSharedQuadState(); |
| second_shared_state->overlay_damage_index = 1; |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| // Set the protected video flag will force the quad to use hw overlay |
| video_quad->protected_video_type = |
| gfx::ProtectedVideoType::kHardwareProtected; |
| |
| SharedQuadState* third_shared_state = |
| pass->CreateAndAppendSharedQuadState(); |
| third_shared_state->overlay_damage_index = 2; |
| auto* second_video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| second_video_quad->protected_video_type = |
| gfx::ProtectedVideoType::kHardwareProtected; |
| second_video_quad->rect.set_origin(gfx::Point(2, 2)); |
| second_video_quad->visible_rect.set_origin(gfx::Point(2, 2)); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(1, 1, 10, 10); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(1, 1, 10, 10), gfx::Rect(0, 0, 0, 0), gfx::Rect(0, 0, 0, 0)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| EXPECT_EQ(2U, dc_layer_list.size()); |
| EXPECT_EQ(-1, dc_layer_list.front().plane_z_order); |
| EXPECT_EQ(-2, dc_layer_list.back().plane_z_order); |
| |
| // The underlay rectangle is the same, so the damage for first video quad is |
| // contained within the combined occluding rects for this and the last |
| // frame. Second video quad also adds its damage. |
| EXPECT_EQ(gfx::Rect(1, 1, 10, 10), damage_rect_); |
| } |
| } |
| |
| TEST_P(DCLayerOverlayTest, DamageRectWithoutVideoDamage) { |
| { |
| auto pass = CreateRenderPass(); |
| SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back(); |
| shared_quad_state->overlay_damage_index = 0; |
| // Occluding quad fully contained in video rect. |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 3, 100, 100), SkColors::kWhite); |
| // Non-occluding quad fully outside video rect |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(210, 210, 20, 20), SkColors::kWhite); |
| |
| // Underlay video quad |
| SharedQuadState* second_shared_state = |
| pass->CreateAndAppendSharedQuadState(); |
| second_shared_state->overlay_damage_index = 1; |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| video_quad->rect = gfx::Rect(0, 0, 200, 200); |
| video_quad->visible_rect = video_quad->rect; |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| // Damage rect fully outside video quad |
| damage_rect_ = gfx::Rect(210, 210, 20, 20); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(210, 210, 20, 20), gfx::Rect(0, 0, 0, 0)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(-1, dc_layer_list.back().plane_z_order); |
| // All rects must be redrawn at the first frame. |
| EXPECT_EQ(gfx::Rect(0, 0, 230, 230), damage_rect_); |
| } |
| { |
| auto pass = CreateRenderPass(); |
| SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back(); |
| shared_quad_state->overlay_damage_index = 0; |
| // Occluding quad fully contained in video rect. |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 3, 100, 100), SkColors::kWhite); |
| // Non-occluding quad fully outside video rect |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(210, 210, 20, 20), SkColors::kWhite); |
| |
| // Underlay video quad |
| SharedQuadState* second_shared_state = |
| pass->CreateAndAppendSharedQuadState(); |
| second_shared_state->overlay_damage_index = 1; |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| video_quad->rect = gfx::Rect(0, 0, 200, 200); |
| video_quad->visible_rect = video_quad->rect; |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| // Damage rect fully outside video quad |
| damage_rect_ = gfx::Rect(210, 210, 20, 20); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(210, 210, 20, 20), gfx::Rect(0, 0, 0, 0)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(-1, dc_layer_list.back().plane_z_order); |
| // Only the non-overlay damaged rect need to be drawn by the gl compositor |
| EXPECT_EQ(gfx::Rect(210, 210, 20, 20), damage_rect_); |
| } |
| } |
| |
| TEST_P(DCLayerOverlayTest, DamageRect) { |
| for (int i = 0; i < 2; i++) { |
| auto pass = CreateRenderPass(); |
| SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back(); |
| shared_quad_state->overlay_damage_index = 0; |
| CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(1, 1, 10, 10); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 1, 10, 10)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(1, dc_layer_list.back().plane_z_order); |
| // Damage rect should be unchanged on initial frame because of resize, but |
| // should be empty on the second frame because everything was put in a |
| // layer. |
| if (i == 1) |
| EXPECT_TRUE(damage_rect_.IsEmpty()); |
| else |
| EXPECT_EQ(gfx::Rect(1, 1, 10, 10), damage_rect_); |
| } |
| } |
| |
| TEST_P(DCLayerOverlayTest, ClipRect) { |
| // Process twice. The second time through the overlay list shouldn't change, |
| // which will allow the damage rect to reflect just the changes in that |
| // frame. |
| for (size_t i = 0; i < 2; ++i) { |
| auto pass = CreateRenderPass(); |
| pass->shared_quad_state_list.back()->overlay_damage_index = 0; |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 2, 100, 100), SkColors::kWhite); |
| pass->shared_quad_state_list.back()->clip_rect = gfx::Rect(0, 3, 100, 100); |
| |
| SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState(); |
| shared_state->opacity = 1.f; |
| shared_state->overlay_damage_index = 1; |
| CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), shared_state, pass.get()); |
| // Clipped rect shouldn't be overlapped by clipped opaque quad rect. |
| shared_state->clip_rect = gfx::Rect(0, 0, 100, 3); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| damage_rect_ = gfx::Rect(1, 1, 10, 10); |
| SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 3, 10, 8), |
| gfx::Rect(1, 1, 10, 2)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| // Because of clip rects the overlay isn't occluded and shouldn't be an |
| // underlay. |
| EXPECT_EQ(1, dc_layer_list.back().plane_z_order); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 3), dc_layer_list.back().clip_rect); |
| if (i == 1) { |
| // The damage rect should only contain contents that aren't in the |
| // clipped overlay rect. |
| EXPECT_EQ(gfx::Rect(1, 3, 10, 8), damage_rect_); |
| } |
| } |
| } |
| |
| TEST_P(DCLayerOverlayTest, TransparentOnTop) { |
| // Process twice. The second time through the overlay list shouldn't change, |
| // which will allow the damage rect to reflect just the changes in that |
| // frame. |
| for (size_t i = 0; i < 2; ++i) { |
| auto pass = CreateRenderPass(); |
| pass->shared_quad_state_list.back()->overlay_damage_index = 0; |
| CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| pass->shared_quad_state_list.back()->opacity = 0.5f; |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(1, 1, 10, 10); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 1, 10, 10)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(1, dc_layer_list.back().plane_z_order); |
| // Quad isn't opaque, so underlying damage must remain the same. |
| EXPECT_EQ(gfx::Rect(1, 1, 10, 10), damage_rect_); |
| } |
| } |
| |
| TEST_P(DCLayerOverlayTest, UnderlayDamageRectWithQuadOnTopUnchanged) { |
| for (int i = 0; i < 3; i++) { |
| auto pass = CreateRenderPass(); |
| // Add a solid color quad on top |
| SharedQuadState* shared_state_on_top = pass->shared_quad_state_list.back(); |
| CreateSolidColorQuadAt(shared_state_on_top, SkColors::kRed, pass.get(), |
| kOverlayBottomRightRect); |
| |
| SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState(); |
| shared_state->opacity = 1.f; |
| CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), shared_state, pass.get()); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| gfx::Rect damage_rect_ = kOverlayRect; |
| shared_state->overlay_damage_index = 1; |
| |
| // The quad on top does not give damage on the third frame |
| SurfaceDamageRectList surface_damage_rect_list = {kOverlayBottomRightRect, |
| kOverlayRect}; |
| if (i == 2) { |
| surface_damage_rect_list[0] = gfx::Rect(); |
| } |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(-1, dc_layer_list.back().plane_z_order); |
| // Damage rect should be unchanged on initial frame, but should be reduced |
| // to the size of quad on top, and empty on the third frame. |
| if (i == 0) |
| EXPECT_EQ(kOverlayRect, damage_rect_); |
| else if (i == 1) |
| EXPECT_EQ(kOverlayBottomRightRect, damage_rect_); |
| else if (i == 2) |
| EXPECT_EQ(gfx::Rect(), damage_rect_); |
| } |
| } |
| |
| // Test whether quads with rounded corners are supported. |
| TEST_P(DCLayerOverlayTest, RoundedCorners) { |
| // Frame #0 |
| { |
| auto pass = CreateRenderPass(); |
| |
| // Create a video YUV quad with rounded corner, nothing on top. |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| gfx::Rect rect(0, 0, 256, 256); |
| video_quad->rect = rect; |
| video_quad->visible_rect = rect; |
| pass->shared_quad_state_list.back()->overlay_damage_index = 0; |
| // Rounded corners |
| pass->shared_quad_state_list.back()->mask_filter_info = |
| gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(0.f, 0.f, 20.f, 30.f), 5.f)); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(0, 0, 256, 256); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(0, 0, 256, 256)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| auto* root_pass = pass_list.back().get(); |
| auto* replaced_quad = root_pass->quad_list.back(); |
| auto* replaced_sqs = replaced_quad->shared_quad_state; |
| |
| // The video should be forced to an underlay mode, even there is nothing on |
| // top. |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(-1, dc_layer_list.back().plane_z_order); |
| |
| // Check whether there is a replaced quad in the quad list. |
| EXPECT_EQ(1U, root_pass->quad_list.size()); |
| |
| // Check whether blend mode == kDstOut, color == black and still have the |
| // rounded corner mask filter for the replaced solid quad. |
| EXPECT_EQ(replaced_sqs->blend_mode, SkBlendMode::kDstOut); |
| EXPECT_EQ(SolidColorDrawQuad::MaterialCast(replaced_quad)->color, |
| SkColors::kBlack); |
| EXPECT_TRUE(replaced_sqs->mask_filter_info.HasRoundedCorners()); |
| |
| // The whole frame is damaged. |
| EXPECT_EQ(gfx::Rect(0, 0, 256, 256), damage_rect_); |
| } |
| |
| // Frame #1 |
| { |
| auto pass = CreateRenderPass(); |
| // Create a solid quad. |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 0, 32, 32), SkColors::kRed); |
| |
| // Create a video YUV quad with rounded corners below the red solid quad. |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| gfx::Rect rect(0, 0, 256, 256); |
| video_quad->rect = rect; |
| video_quad->visible_rect = rect; |
| pass->shared_quad_state_list.back()->overlay_damage_index = 1; |
| // Rounded corners |
| pass->shared_quad_state_list.back()->mask_filter_info = |
| gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(0.f, 0.f, 20.f, 30.f), 5.f)); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(0, 0, 256, 256); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(0, 0, 32, 32), gfx::Rect(0, 0, 256, 256)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| auto* root_pass = pass_list.back().get(); |
| auto* replaced_quad = root_pass->quad_list.back(); |
| auto* replaced_sqs = replaced_quad->shared_quad_state; |
| |
| // still in an underlay mode. |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(-1, dc_layer_list.back().plane_z_order); |
| |
| // Check whether the red quad on top and the replacedment of the YUV quad |
| // are still in the render pass. |
| EXPECT_EQ(2U, root_pass->quad_list.size()); |
| |
| // Check whether blend mode is kDstOut, color is black, and still have the |
| // rounded corner mask filter for the replaced solid quad. |
| EXPECT_EQ(replaced_sqs->blend_mode, SkBlendMode::kDstOut); |
| EXPECT_EQ(SolidColorDrawQuad::MaterialCast(replaced_quad)->color, |
| SkColors::kBlack); |
| EXPECT_TRUE(replaced_sqs->mask_filter_info.HasRoundedCorners()); |
| |
| // Only the UI is damaged. |
| EXPECT_EQ(gfx::Rect(0, 0, 32, 32), damage_rect_); |
| } |
| |
| // Frame #2 |
| { |
| auto pass = CreateRenderPass(); |
| // Create a solid quad. |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 0, 32, 32), SkColors::kRed); |
| |
| // Create a video YUV quad with rounded corners below the red solid quad. |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| gfx::Rect rect(0, 0, 256, 256); |
| video_quad->rect = rect; |
| video_quad->visible_rect = rect; |
| pass->shared_quad_state_list.back()->overlay_damage_index = 0; |
| // Rounded corners |
| pass->shared_quad_state_list.back()->mask_filter_info = |
| gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(0.f, 0.f, 20.f, 30.f), 5.f)); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(0, 0, 256, 256); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(0, 0, 256, 256)}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| auto* root_pass = pass_list.back().get(); |
| auto* replaced_quad = root_pass->quad_list.back(); |
| auto* replaced_sqs = replaced_quad->shared_quad_state; |
| |
| // still in an underlay mode. |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(-1, dc_layer_list.back().plane_z_order); |
| |
| // Check whether the red quad on top and the replacedment of the YUV quad |
| // are still in the render pass. |
| EXPECT_EQ(2U, root_pass->quad_list.size()); |
| |
| // Check whether blend mode is kDstOut and color is black for the replaced |
| // solid quad. |
| EXPECT_EQ(replaced_sqs->blend_mode, SkBlendMode::kDstOut); |
| EXPECT_EQ(SolidColorDrawQuad::MaterialCast(replaced_quad)->color, |
| SkColors::kBlack); |
| EXPECT_TRUE(replaced_sqs->mask_filter_info.HasRoundedCorners()); |
| |
| // Zero root damage rect. |
| EXPECT_TRUE(damage_rect_.IsEmpty()); |
| } |
| } |
| |
| // If there are multiple yuv overlay quad candidates, no overlay will be |
| // promoted to save power. |
| TEST_P(DCLayerOverlayTest, MultipleYUVOverlays) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature( |
| features::kNoUndamagedOverlayPromotion); |
| { |
| auto pass = CreateRenderPass(); |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 0, 256, 256), SkColors::kWhite); |
| |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| gfx::Rect rect(10, 10, 80, 80); |
| video_quad->rect = rect; |
| video_quad->visible_rect = rect; |
| pass->shared_quad_state_list.back()->overlay_damage_index = 1; |
| |
| auto* second_video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| gfx::Rect second_rect(100, 100, 120, 120); |
| second_video_quad->rect = second_rect; |
| second_video_quad->visible_rect = second_rect; |
| pass->shared_quad_state_list.back()->overlay_damage_index = 2; |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(0, 0, 220, 220); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list; |
| surface_damage_rect_list.push_back(gfx::Rect(0, 0, 256, 256)); |
| surface_damage_rect_list.push_back(video_quad->rect); |
| surface_damage_rect_list.push_back(second_video_quad->rect); |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| // Skip overlays. |
| EXPECT_EQ(0U, dc_layer_list.size()); |
| EXPECT_EQ(gfx::Rect(0, 0, 220, 220), damage_rect_); |
| |
| // Check whether all 3 quads including two YUV quads are still in the render |
| // pass |
| auto* root_pass = pass_list.back().get(); |
| int quad_count = root_pass->quad_list.size(); |
| EXPECT_EQ(3, quad_count); |
| } |
| } |
| |
| TEST_P(DCLayerOverlayTest, SetEnableDCLayers) { |
| // Start without DC layers. |
| overlay_processor_->set_using_dc_layers_for_testing(false); |
| |
| // Draw 60 frames with overlay video quads. |
| for (int i = 0; i < 60; i++) { |
| auto pass = CreateRenderPass(); |
| CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| SurfaceDamageRectList surface_damage_rect_list; |
| damage_rect_ = gfx::Rect(1, 1, 10, 10); |
| |
| // There will be full damage and SetEnableDCLayers(true) will be called on |
| // the first frame. |
| const gfx::Rect expected_damage = |
| (i == 0) ? pass_list.back()->output_rect : gfx::Rect(); |
| |
| if (IsUsingDCompPresenter()) { |
| EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(_)).Times(0); |
| } else { |
| if (i == 0) { |
| EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(true)).Times(1); |
| } else { |
| EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(_)).Times(0); |
| } |
| } |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| if (IsUsingDCompPresenter() && i == 0) { |
| EXPECT_TRUE(pass_list.back()->needs_synchronous_dcomp_commit); |
| EXPECT_TRUE(pass_list.back()->has_transparent_background); |
| ASSERT_TRUE(output_surface_plane_.has_value()); |
| EXPECT_TRUE(output_surface_plane_->enable_blending); |
| } |
| |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| EXPECT_EQ(1, dc_layer_list.back().plane_z_order); |
| EXPECT_EQ(damage_rect_, expected_damage); |
| |
| Mock::VerifyAndClearExpectations(output_surface_.get()); |
| } |
| |
| // Draw 65 frames without overlays. |
| for (int i = 0; i < 65; i++) { |
| auto pass = CreateRenderPass(); |
| |
| damage_rect_ = gfx::Rect(1, 1, 10, 10); |
| auto* quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| quad->SetNew(pass->CreateAndAppendSharedQuadState(), damage_rect_, |
| damage_rect_, SkColors::kRed, false); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list; |
| |
| damage_rect_ = gfx::Rect(1, 1, 10, 10); |
| |
| // There will be full damage and SetEnableDCLayers(false) will be called |
| // after 60 consecutive frames with no overlays. The first frame without |
| // overlays will also have full damage, but no call to SetEnableDCLayers. |
| const gfx::Rect expected_damage = (i == 0 || (i + 1) == 60) |
| ? pass_list.back()->output_rect |
| : damage_rect_; |
| |
| if (IsUsingDCompPresenter()) { |
| EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(_)).Times(0); |
| } else { |
| if (i + 1 == 60) { |
| EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(false)).Times(1); |
| } else { |
| EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(_)).Times(0); |
| } |
| } |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| if (IsUsingDCompPresenter() && i + 1 == 60) { |
| EXPECT_FALSE(pass_list.back()->needs_synchronous_dcomp_commit); |
| EXPECT_FALSE(pass_list.back()->has_transparent_background); |
| ASSERT_TRUE(output_surface_plane_.has_value()); |
| EXPECT_FALSE(output_surface_plane_->enable_blending); |
| } |
| |
| EXPECT_EQ(0u, dc_layer_list.size()); |
| EXPECT_EQ(damage_rect_, expected_damage); |
| |
| Mock::VerifyAndClearExpectations(output_surface_.get()); |
| } |
| } |
| |
| // Test that the video is forced to underlay if the expanded quad of pixel |
| // moving foreground filter is on top. |
| TEST_P(DCLayerOverlayTest, PixelMovingForegroundFilter) { |
| AggregatedRenderPassList pass_list; |
| |
| // Create a non-root render pass with a pixel-moving foreground filter. |
| AggregatedRenderPassId filter_render_pass_id{2}; |
| gfx::Rect filter_rect = gfx::Rect(260, 260, 100, 100); |
| cc::FilterOperations blur_filter; |
| blur_filter.Append(cc::FilterOperation::CreateBlurFilter(10.f)); |
| auto filter_pass = std::make_unique<AggregatedRenderPass>(); |
| filter_pass->SetNew(filter_render_pass_id, filter_rect, filter_rect, |
| gfx::Transform()); |
| filter_pass->filters = blur_filter; |
| |
| // Add a solid quad to the non-root pass. |
| SharedQuadState* shared_state_filter = |
| filter_pass->CreateAndAppendSharedQuadState(); |
| CreateSolidColorQuadAt(shared_state_filter, SkColors::kRed, filter_pass.get(), |
| filter_rect); |
| shared_state_filter->opacity = 1.f; |
| pass_list.push_back(std::move(filter_pass)); |
| |
| // Create a root render pass. |
| auto pass = CreateRenderPass(); |
| // Add a RenderPassDrawQuad to the root render pass. |
| SharedQuadState* shared_quad_state_rpdq = pass->shared_quad_state_list.back(); |
| // The pixel-moving render pass draw quad itself (rpdq->rect) doesn't |
| // intersect with kOverlayRect(0, 0, 256, 256), but the expanded draw quad |
| // (rpdq->rect(260, 260, 100, 100) + MaximumPixelMovement (2 * 10.f) = (240, |
| // 240, 140, 140)) does. |
| |
| CreateRenderPassDrawQuadAt(pass.get(), shared_quad_state_rpdq, filter_rect, |
| filter_render_pass_id); |
| |
| // Add a video quad to the root render pass. |
| SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState(); |
| shared_state->opacity = 1.f; |
| CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), shared_state, pass.get()); |
| // Make the root render pass output rect bigger enough to cover the video |
| // quad kOverlayRect(0, 0, 256, 256) and the render pass draw quad (260, 260, |
| // 100, 100). |
| pass->output_rect = gfx::Rect(0, 0, 512, 512); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| render_pass_filters[filter_render_pass_id] = &blur_filter; |
| |
| pass_list.push_back(std::move(pass)); |
| // filter_rect + kOverlayRect. Both are damaged. |
| gfx::Rect damage_rect_ = gfx::Rect(0, 0, 360, 360); |
| shared_state->overlay_damage_index = 1; |
| |
| SurfaceDamageRectList surface_damage_rect_list = {filter_rect, kOverlayRect}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| // Make sure the video is in an underlay mode if the overlay quad intersects |
| // with (rpdq->rect + MaximumPixelMovement()). |
| EXPECT_EQ(-1, dc_layer_list.back().plane_z_order); |
| EXPECT_EQ(gfx::Rect(0, 0, 360, 360), damage_rect_); |
| } |
| |
| // Test that the video is not promoted if a quad on top has backdrop filters. |
| TEST_P(DCLayerOverlayTest, BackdropFilter) { |
| AggregatedRenderPassList pass_list; |
| |
| // Create a non-root render pass with a backdrop filter. |
| AggregatedRenderPassId backdrop_filter_render_pass_id{2}; |
| gfx::Rect backdrop_filter_rect = gfx::Rect(200, 200, 100, 100); |
| cc::FilterOperations backdrop_filter; |
| backdrop_filter.Append(cc::FilterOperation::CreateBlurFilter(10.f)); |
| auto backdrop_filter_pass = std::make_unique<AggregatedRenderPass>(); |
| backdrop_filter_pass->SetNew(backdrop_filter_render_pass_id, |
| backdrop_filter_rect, backdrop_filter_rect, |
| gfx::Transform()); |
| backdrop_filter_pass->backdrop_filters = backdrop_filter; |
| |
| // Add a transparent solid quad to the non-root pass. |
| SharedQuadState* shared_state_backdrop_filter = |
| backdrop_filter_pass->CreateAndAppendSharedQuadState(); |
| CreateSolidColorQuadAt(shared_state_backdrop_filter, SkColors::kGreen, |
| backdrop_filter_pass.get(), backdrop_filter_rect); |
| shared_state_backdrop_filter->opacity = 0.1f; |
| pass_list.push_back(std::move(backdrop_filter_pass)); |
| |
| // Create a root render pass. |
| auto pass = CreateRenderPass(); |
| // Add a RenderPassDrawQuad to the root render pass, on top of the video. |
| SharedQuadState* shared_quad_state_rpdq = pass->shared_quad_state_list.back(); |
| shared_quad_state_rpdq->opacity = 0.1f; |
| // The render pass draw quad rpdq->rect intersects with the overlay quad |
| // kOverlayRect(0, 0, 256, 256). |
| CreateRenderPassDrawQuadAt(pass.get(), shared_quad_state_rpdq, |
| backdrop_filter_rect, |
| backdrop_filter_render_pass_id); |
| |
| // Add a video quad to the root render pass. |
| SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState(); |
| shared_state->opacity = 1.f; |
| CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), shared_state, pass.get()); |
| // Make the root render pass output rect bigger enough to cover the video |
| // quad kOverlayRect(0, 0, 256, 256) and the render pass draw quad (200, 200, |
| // 100, 100). |
| pass->output_rect = gfx::Rect(0, 0, 512, 512); |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| render_pass_backdrop_filters[backdrop_filter_render_pass_id] = |
| &backdrop_filter; |
| |
| pass_list.push_back(std::move(pass)); |
| // backdrop_filter_rect + kOverlayRect. Both are damaged. |
| gfx::Rect damage_rect_ = gfx::Rect(0, 0, 300, 300); |
| shared_state->overlay_damage_index = 1; |
| |
| SurfaceDamageRectList surface_damage_rect_list = {backdrop_filter_rect, |
| kOverlayRect}; |
| |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| // Make sure the video is not promoted if the overlay quad intersects |
| // with the backdrop filter rpdq->rect. |
| EXPECT_EQ(0U, dc_layer_list.size()); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 300), damage_rect_); |
| } |
| |
| // Test if overlay is not used when video capture is on. |
| TEST_P(DCLayerOverlayTest, VideoCapture) { |
| // Frame #0 |
| { |
| auto pass = CreateRenderPass(); |
| pass->shared_quad_state_list.back(); |
| // Create a solid quad. |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 0, 32, 32), SkColors::kRed); |
| |
| // Create a video YUV quad below the red solid quad. |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| gfx::Rect rect(0, 0, 256, 256); |
| video_quad->rect = rect; |
| video_quad->visible_rect = rect; |
| pass->shared_quad_state_list.back()->overlay_damage_index = 1; |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(0, 0, 256, 256); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(0, 0, 32, 32), gfx::Rect(0, 0, 256, 256)}; |
| // No video capture in this frame. |
| overlay_processor_->SetIsVideoCaptureEnabled(false); |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| // Use overlay for the video quad. |
| EXPECT_EQ(1U, dc_layer_list.size()); |
| } |
| |
| // Frame #1 |
| { |
| auto pass = CreateRenderPass(); |
| pass->shared_quad_state_list.back(); |
| // Create a solid quad. |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| gfx::Rect(0, 0, 32, 32), SkColors::kRed); |
| |
| // Create a video YUV quad below the red solid quad. |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| gfx::Rect rect(0, 0, 256, 256); |
| video_quad->rect = rect; |
| video_quad->visible_rect = rect; |
| pass->shared_quad_state_list.back()->overlay_damage_index = 0; |
| |
| OverlayCandidateList dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| damage_rect_ = gfx::Rect(0, 0, 256, 256); |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = { |
| gfx::Rect(0, 0, 256, 256)}; |
| |
| // Now video capture is enabled. |
| overlay_processor_->SetIsVideoCaptureEnabled(true); |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| |
| // Should not use overlay for the video when video capture is on. |
| EXPECT_EQ(0U, dc_layer_list.size()); |
| |
| // Check whether both quads including the YUV quads are still in the render |
| // pass. |
| auto* root_pass = pass_list.back().get(); |
| int quad_count = root_pass->quad_list.size(); |
| EXPECT_EQ(2, quad_count); |
| } |
| } |
| |
| TEST_P(DCLayerOverlayTest, RenderPassRootTransformOverlay) { |
| TestRenderPassRootTransform(/*is_overlay*/ true); |
| } |
| |
| TEST_P(DCLayerOverlayTest, RenderPassRootTransformUnderlay) { |
| TestRenderPassRootTransform(/*is_overlay*/ false); |
| } |
| |
| // Tests processing overlays/underlays in a render pass that contains a |
| // non-identity transform to root. |
| void DCLayerOverlayTest::TestRenderPassRootTransform(bool is_overlay) { |
| const gfx::Rect kOutputRect = gfx::Rect(0, 0, 256, 256); |
| const gfx::Rect kVideoRect = gfx::Rect(0, 0, 100, 100); |
| const gfx::Rect kOpaqueRect = gfx::Rect(90, 80, 15, 30); |
| const gfx::Transform kRenderPassToRootTransform = |
| gfx::Transform::MakeTranslation(27, 45); |
| const SurfaceDamageRectList kSurfaceDamageRectList = { |
| gfx::Rect(25, 20, 10, 10), // above overlay |
| gfx::Rect(27, 45, 100, 100), // damage rect of video overlay |
| gfx::Rect(30, 25, 50, 50)}; // below overlay |
| const size_t kOverlayDamageIndex = 1; |
| |
| for (size_t frame = 0; frame < 3; frame++) { |
| auto pass = CreateRenderPass(); |
| pass->transform_to_root_target = kRenderPassToRootTransform; |
| pass->shared_quad_state_list.back()->overlay_damage_index = |
| kOverlayDamageIndex; |
| |
| if (!is_overlay) { |
| // Create a quad that occludes the video to force it to an underlay. |
| CreateOpaqueQuadAt(resource_provider_.get(), |
| pass->shared_quad_state_list.back(), pass.get(), |
| kOpaqueRect, SkColors::kWhite); |
| } |
| |
| auto* video_quad = CreateFullscreenCandidateYUVVideoQuad( |
| resource_provider_.get(), child_resource_provider_.get(), |
| child_provider_.get(), pass->shared_quad_state_list.back(), pass.get()); |
| video_quad->rect = gfx::Rect(kVideoRect); |
| video_quad->visible_rect = video_quad->rect; |
| |
| std::vector<OverlayCandidate> dc_layer_list; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_filters; |
| OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters; |
| AggregatedRenderPassList pass_list; |
| pass_list.push_back(std::move(pass)); |
| SurfaceDamageRectList surface_damage_rect_list = kSurfaceDamageRectList; |
| |
| damage_rect_ = kOutputRect; |
| overlay_processor_->ProcessForOverlays( |
| resource_provider_.get(), &pass_list, GetIdentityColorMatrix(), |
| render_pass_filters, render_pass_backdrop_filters, |
| std::move(surface_damage_rect_list), GetOutputSurfacePlane(), |
| &dc_layer_list, &damage_rect_, &content_bounds_); |
| LOG(INFO) << damage_rect_.ToString(); |
| |
| EXPECT_EQ(dc_layer_list.size(), 1u); |
| EXPECT_TRUE( |
| absl::holds_alternative<gfx::Transform>(dc_layer_list[0].transform)); |
| EXPECT_EQ(absl::get<gfx::Transform>(dc_layer_list[0].transform), |
| kRenderPassToRootTransform); |
| if (is_overlay) { |
| EXPECT_GT(dc_layer_list[0].plane_z_order, 0); |
| } else { |
| EXPECT_LT(dc_layer_list[0].plane_z_order, 0); |
| } |
| |
| if (frame == 0) { |
| // On the first frame, the damage rect should be unchanged since the |
| // overlays are being processed for the first time. |
| EXPECT_EQ(gfx::Rect(0, 0, 256, 256), damage_rect_); |
| } else { |
| // With the render pass to root transform, the video overlay should have |
| // been translated to (27,45 100x100). The final damage rect should |
| // include (25,20 10x10), which doesn't intersect the overlay. The |
| // (30,25 50x50) surface damage is partially under the overlay, so the |
| // overlay damage can be subtracted to become (30,25 50x15). The final |
| // damage rect is (25,20 10x10) union (30,25 50x15). |
| EXPECT_EQ(damage_rect_, gfx::Rect(25, 20, 55, 25)); |
| } |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| DCLayerOverlayTest, |
| testing::Bool(), |
| &DCLayerOverlayTest::GetParamName); |
| |
| } // namespace |
| } // namespace viz |