blob: acf80bf08b41485e398822fadceaa472337c800e [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/display/resolved_frame_data.h"
#include <memory>
#include <utility>
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/service/display/display_resource_provider_software.h"
#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/test/compositor_frame_helpers.h"
#include "components/viz/test/draw_quad_matchers.h"
#include "components/viz/test/test_surface_id_allocator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace viz {
namespace {
constexpr gfx::Rect kOutputRect(100, 100);
CompositorFrame MakeSimpleFrame(const gfx::Rect& damage_rect = kOutputRect) {
return CompositorFrameBuilder()
.AddRenderPass(RenderPassBuilder(kOutputRect)
.AddSolidColorQuad(kOutputRect, SkColors::kRed)
.SetDamageRect(kOutputRect))
.Build();
}
std::unique_ptr<CompositorRenderPass> BuildRenderPass(int id) {
auto render_pass = CompositorRenderPass::Create();
render_pass->SetNew(CompositorRenderPassId::FromUnsafeValue(id), kOutputRect,
kOutputRect, gfx::Transform());
return render_pass;
}
void AddRenderPassQuad(CompositorRenderPass* render_pass,
CompositorRenderPassId render_pass_id) {
auto* sqs = render_pass->CreateAndAppendSharedQuadState();
sqs->SetAll(gfx::Transform(), kOutputRect, kOutputRect, gfx::MaskFilterInfo(),
absl::nullopt, /*are_contents_opaque=*/false, 1,
SkBlendMode::kSrcOver, 0);
auto* quad =
render_pass->CreateAndAppendDrawQuad<CompositorRenderPassDrawQuad>();
quad->SetNew(sqs, kOutputRect, kOutputRect, render_pass_id,
kInvalidResourceId, gfx::RectF(), gfx::Size(), gfx::Vector2dF(),
gfx::PointF(), gfx::RectF(),
/*force_anti_aliasing_off=*/false,
/*backdrop_filter_quality=*/1.0f);
}
class ResolvedFrameDataTest : public testing::Test {
public:
ResolvedFrameDataTest()
: support_(std::make_unique<CompositorFrameSinkSupport>(
nullptr,
&frame_sink_manager_,
surface_id_.frame_sink_id(),
/*is_root=*/true)) {}
protected:
// Submits a CompositorFrame so there is a fully populated surface with an
// active CompositorFrame. Returns the corresponding surface.
Surface* SubmitCompositorFrame(CompositorFrame frame) {
support_->SubmitCompositorFrame(surface_id_.local_surface_id(),
std::move(frame));
Surface* surface =
frame_sink_manager_.surface_manager()->GetSurfaceForId(surface_id_);
EXPECT_TRUE(surface);
EXPECT_TRUE(surface->HasActiveFrame());
return surface;
}
ServerSharedBitmapManager shared_bitmap_manager_;
DisplayResourceProviderSoftware resource_provider_{&shared_bitmap_manager_};
FrameSinkManagerImpl frame_sink_manager_{
FrameSinkManagerImpl::InitParams(&shared_bitmap_manager_)};
TestSurfaceIdAllocator surface_id_{FrameSinkId(1, 1)};
std::unique_ptr<CompositorFrameSinkSupport> support_;
AggregatedRenderPassId::Generator render_pass_id_generator_;
};
// Submits a CompositorFrame with three valid render passes then checks that
// ResolvedPassData is valid and has the correct data.
TEST_F(ResolvedFrameDataTest, UpdateActiveFrame) {
auto frame = CompositorFrameBuilder()
.AddRenderPass(BuildRenderPass(101))
.AddRenderPass(BuildRenderPass(102))
.AddRenderPass(BuildRenderPass(103))
.Build();
Surface* surface = SubmitCompositorFrame(std::move(frame));
ResolvedFrameData resolved_frame(&resource_provider_, surface, 0u,
AggregatedRenderPassId());
// The resolved frame should be false after construction.
EXPECT_FALSE(resolved_frame.is_valid());
// Resolved frame data should be valid after adding resolved render pass data
// and have three render passes.
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
EXPECT_TRUE(resolved_frame.is_valid());
EXPECT_THAT(resolved_frame.GetResolvedPasses(), testing::SizeIs(3));
// Looking up ResolvedPassData by CompositorRenderPassId should work.
for (auto& render_pass : surface->GetActiveFrame().render_pass_list) {
const ResolvedPassData& resolved_pass =
resolved_frame.GetRenderPassDataById(render_pass->id);
EXPECT_EQ(&resolved_pass.render_pass(), render_pass.get());
}
// Check invalidation also works.
resolved_frame.SetInvalid();
EXPECT_FALSE(resolved_frame.is_valid());
}
// Constructs a CompositorFrame with two render passes that have the same id.
// Verifies the frame is rejected as invalid.
TEST_F(ResolvedFrameDataTest, DupliateRenderPassIds) {
auto frame = CompositorFrameBuilder()
.AddRenderPass(BuildRenderPass(1))
.AddRenderPass(BuildRenderPass(1))
.Build();
Surface* surface = SubmitCompositorFrame(std::move(frame));
ResolvedFrameData resolved_frame(&resource_provider_, surface, 0u,
AggregatedRenderPassId());
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
EXPECT_FALSE(resolved_frame.is_valid());
}
// Constructs a CompositorFrame with render pass that tries to embed itself
// forming a cycle. Verifies the frame is rejected as invalid.
TEST_F(ResolvedFrameDataTest, RenderPassIdsSelfCycle) {
// Create a CompositorFrame and submit it to |surface_id| so there is a
// fully populated Surface with an active CompositorFrame.
auto render_pass = BuildRenderPass(1);
AddRenderPassQuad(render_pass.get(), render_pass->id);
auto frame =
CompositorFrameBuilder().AddRenderPass(std::move(render_pass)).Build();
Surface* surface = SubmitCompositorFrame(std::move(frame));
ResolvedFrameData resolved_frame(&resource_provider_, surface, 0u,
AggregatedRenderPassId());
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
EXPECT_FALSE(resolved_frame.is_valid());
}
// Constructs a CompositorFrame with two render pass that tries to embed each
// other forming a cycle. Verifies the frame is rejected as invalid.
TEST_F(ResolvedFrameDataTest, RenderPassIdsCycle) {
// Create a CompositorFrame and submit it to |surface_id| so there is a
// fully populated Surface with an active CompositorFrame.
auto render_pass1 = BuildRenderPass(1);
auto render_pass2 = BuildRenderPass(2);
AddRenderPassQuad(render_pass1.get(), render_pass2->id);
AddRenderPassQuad(render_pass2.get(), render_pass1->id);
auto frame = CompositorFrameBuilder()
.AddRenderPass(std::move(render_pass1))
.AddRenderPass(std::move(render_pass2))
.Build();
Surface* surface = SubmitCompositorFrame(std::move(frame));
ResolvedFrameData resolved_frame(&resource_provider_, surface, 0u,
AggregatedRenderPassId());
// RenderPasses have duplicate IDs so the resolved frame should be marked as
// invalid.
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
EXPECT_FALSE(resolved_frame.is_valid());
}
// Check GetRectDamage() handles per quad damage correctly.
TEST_F(ResolvedFrameDataTest, RenderPassWithPerQuadDamage) {
constexpr gfx::Rect pass_damage_rect(80, 80, 10, 10);
constexpr gfx::Rect quad_damage_rect(10, 10, 20, 20);
constexpr ResourceId resource_id(1);
auto frame =
CompositorFrameBuilder()
.AddRenderPass(RenderPassBuilder(kOutputRect)
.SetDamageRect(pass_damage_rect)
.AddSolidColorQuad(kOutputRect, SkColors::kRed)
.AddTextureQuad(kOutputRect, resource_id)
.SetQuadDamageRect(quad_damage_rect))
.PopulateResources()
.Build();
Surface* surface = SubmitCompositorFrame(std::move(frame));
EXPECT_EQ(surface->GetActiveFrameIndex(), kFrameIndexStart);
ResolvedFrameData resolved_frame(&resource_provider_, surface, 1u,
AggregatedRenderPassId());
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
ASSERT_TRUE(resolved_frame.is_valid());
resolved_frame.MarkAsUsedInAggregation();
// The damage rect should not include TextureDrawQuad's damage_rect.
EXPECT_EQ(resolved_frame.GetSurfaceDamage(), pass_damage_rect);
// The quads to prewalk should only include the TextureDrawQuad.
EXPECT_THAT(resolved_frame.GetRootRenderPassData().prewalk_quads(),
testing::ElementsAre(IsTextureQuad()));
}
TEST_F(ResolvedFrameDataTest, MarkAsUsed) {
Surface* surface = SubmitCompositorFrame(MakeSimpleFrame());
ResolvedFrameData resolved_frame(&resource_provider_, surface, 0u,
AggregatedRenderPassId());
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
EXPECT_FALSE(resolved_frame.WasUsedInAggregation());
// First aggregation.
resolved_frame.MarkAsUsedInAggregation();
EXPECT_TRUE(resolved_frame.WasUsedInAggregation());
// This is the first frame this aggregation.
EXPECT_EQ(resolved_frame.GetFrameDamageType(), FrameDamageType::kFull);
// Nothing changes if MarkAsUsedInAggregation() is called more than once
// before reset.
resolved_frame.MarkAsUsedInAggregation();
EXPECT_TRUE(resolved_frame.WasUsedInAggregation());
// Reset after aggregation.
resolved_frame.ResetAfterAggregation();
EXPECT_FALSE(resolved_frame.WasUsedInAggregation());
// Don't submit a new frame for the next aggregation.
resolved_frame.MarkAsUsedInAggregation();
EXPECT_TRUE(resolved_frame.WasUsedInAggregation());
EXPECT_EQ(resolved_frame.GetFrameDamageType(), FrameDamageType::kNone);
resolved_frame.ResetAfterAggregation();
// Submit a new frame for the next aggregation.
SubmitCompositorFrame(MakeSimpleFrame());
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
resolved_frame.MarkAsUsedInAggregation();
EXPECT_EQ(resolved_frame.GetFrameDamageType(), FrameDamageType::kFrame);
resolved_frame.ResetAfterAggregation();
// Submit two new frames before the next aggregation. The damage rect from
// last frame aggregated
SubmitCompositorFrame(MakeSimpleFrame());
SubmitCompositorFrame(MakeSimpleFrame());
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
resolved_frame.MarkAsUsedInAggregation();
EXPECT_EQ(resolved_frame.GetFrameDamageType(), FrameDamageType::kFull);
}
// Verifies that SetFullDamageForNextAggregation()
TEST_F(ResolvedFrameDataTest, SetFullDamageNextAggregation) {
Surface* surface = SubmitCompositorFrame(MakeSimpleFrame());
ResolvedFrameData resolved_frame(&resource_provider_, surface, 0u,
AggregatedRenderPassId());
// First aggregation to setup existing state.
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
resolved_frame.MarkAsUsedInAggregation();
resolved_frame.ResetAfterAggregation();
resolved_frame.SetFullDamageForNextAggregation();
// Second aggregation with a new frame that has smaller damage rect.
constexpr gfx::Rect damage_rect(10, 10);
SubmitCompositorFrame(MakeSimpleFrame(damage_rect));
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
resolved_frame.MarkAsUsedInAggregation();
// This is the next frame so normally it would use `damage_rect` for damage.
// SetFullDamageForNextAggregation() changes that so the full output_rect is
// damaged.
EXPECT_EQ(resolved_frame.GetFrameDamageType(), FrameDamageType::kFull);
EXPECT_EQ(resolved_frame.GetSurfaceDamage(), kOutputRect);
}
// Verifies that the ResolvedFrameData will reuse a provided root pass ID
TEST_F(ResolvedFrameDataTest, ReusePreviousRootPassId) {
Surface* surface = SubmitCompositorFrame(MakeSimpleFrame());
AggregatedRenderPassId prev_root_pass_id =
render_pass_id_generator_.GenerateNextId();
ResolvedFrameData resolved_frame(&resource_provider_, surface, 0u,
prev_root_pass_id);
resolved_frame.UpdateForActiveFrame(render_pass_id_generator_);
resolved_frame.MarkAsUsedInAggregation();
EXPECT_EQ(resolved_frame.GetRootRenderPassData().remapped_id(),
prev_root_pass_id);
}
} // namespace
} // namespace viz