| // Copyright 2019 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/common/quads/render_pass_io.h" |
| |
| #include <array> |
| #include <memory> |
| #include <string> |
| |
| #include "base/files/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/path_service.h" |
| #include "base/values.h" |
| #include "components/viz/common/quads/compositor_frame.h" |
| #include "components/viz/common/quads/compositor_render_pass_draw_quad.h" |
| #include "components/viz/common/quads/solid_color_draw_quad.h" |
| #include "components/viz/common/quads/surface_draw_quad.h" |
| #include "components/viz/common/quads/tile_draw_quad.h" |
| #include "components/viz/common/quads/video_hole_draw_quad.h" |
| #include "components/viz/test/paths.h" |
| #include "components/viz/test/test_surface_id_allocator.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/modules/skcms/skcms.h" |
| #include "ui/gfx/geometry/rrect_f.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| |
| namespace gfx { |
| struct HDRMetadata; |
| } |
| |
| namespace viz { |
| namespace { |
| |
| TEST(RenderPassIOTest, Default) { |
| auto render_pass0 = CompositorRenderPass::Create(); |
| base::Value::Dict dict0 = CompositorRenderPassToDict(*render_pass0); |
| auto render_pass1 = CompositorRenderPassFromDict(dict0); |
| EXPECT_TRUE(render_pass1); |
| base::Value::Dict dict1 = CompositorRenderPassToDict(*render_pass1); |
| EXPECT_EQ(dict0, dict1); |
| } |
| |
| TEST(RenderPassIOTest, FilterOperations) { |
| auto render_pass0 = CompositorRenderPass::Create(); |
| { |
| // Add two filters. |
| cc::FilterOperation grayscale = |
| cc::FilterOperation::CreateGrayscaleFilter(0.25f); |
| render_pass0->filters.Append(grayscale); |
| cc::FilterOperation opacity = |
| cc::FilterOperation::CreateOpacityFilter(0.8f); |
| render_pass0->filters.Append(opacity); |
| } |
| { |
| // Add three backdrop filters. |
| cc::FilterOperation drop_shadow = |
| cc::FilterOperation::CreateDropShadowFilter(gfx::Point(1.0f, 2.0f), |
| 0.8f, SkColors::kYellow); |
| render_pass0->backdrop_filters.Append(drop_shadow); |
| cc::FilterOperation invert = cc::FilterOperation::CreateInvertFilter(0.64f); |
| render_pass0->backdrop_filters.Append(invert); |
| cc::FilterOperation zoom = cc::FilterOperation::CreateZoomFilter(2.1f, 10); |
| render_pass0->backdrop_filters.Append(zoom); |
| } |
| { |
| // Set backdrop filter bounds. |
| gfx::RRectF rrect(gfx::RectF(2.f, 3.f, 4.f, 5.f), 1.5f); |
| ASSERT_EQ(gfx::RRectF::Type::kSingle, rrect.GetType()); |
| render_pass0->backdrop_filter_bounds = SkPath::RRect(SkRRect::MakeRectXY( |
| gfx::RectFToSkRect(rrect.rect()), rrect.GetSimpleRadii().x(), |
| rrect.GetSimpleRadii().y())); |
| } |
| base::Value::Dict dict0 = CompositorRenderPassToDict(*render_pass0); |
| auto render_pass1 = CompositorRenderPassFromDict(dict0); |
| EXPECT_TRUE(render_pass1); |
| { |
| // Verify two filters are as expected. |
| EXPECT_EQ(render_pass0->filters, render_pass1->filters); |
| EXPECT_EQ(2u, render_pass1->filters.size()); |
| EXPECT_EQ(cc::FilterOperation::GRAYSCALE, |
| render_pass1->filters.at(0).type()); |
| EXPECT_EQ(0.25f, render_pass1->filters.at(0).amount()); |
| EXPECT_EQ(cc::FilterOperation::OPACITY, render_pass1->filters.at(1).type()); |
| EXPECT_EQ(0.8f, render_pass1->filters.at(1).amount()); |
| } |
| { |
| // Verify three backdrop filters are as expected. |
| EXPECT_EQ(render_pass0->backdrop_filters, render_pass1->backdrop_filters); |
| EXPECT_EQ(3u, render_pass1->backdrop_filters.size()); |
| EXPECT_EQ(cc::FilterOperation::DROP_SHADOW, |
| render_pass1->backdrop_filters.at(0).type()); |
| EXPECT_EQ(0.8f, render_pass1->backdrop_filters.at(0).amount()); |
| EXPECT_EQ(SkColors::kYellow, |
| render_pass1->backdrop_filters.at(0).drop_shadow_color()); |
| EXPECT_EQ(gfx::Point(1.0f, 2.0f), |
| render_pass1->backdrop_filters.at(0).offset()); |
| EXPECT_EQ(cc::FilterOperation::INVERT, |
| render_pass1->backdrop_filters.at(1).type()); |
| EXPECT_EQ(0.64f, render_pass1->backdrop_filters.at(1).amount()); |
| EXPECT_EQ(cc::FilterOperation::ZOOM, |
| render_pass1->backdrop_filters.at(2).type()); |
| EXPECT_EQ(2.1f, render_pass1->backdrop_filters.at(2).amount()); |
| EXPECT_EQ(10, render_pass1->backdrop_filters.at(2).zoom_inset()); |
| } |
| { |
| // Verify backdrop filter bounds are as expected. |
| EXPECT_TRUE(render_pass1->backdrop_filter_bounds.has_value()); |
| SkRRect backdrop_filter_as_rect_0; |
| SkRRect backdrop_filter_as_rect_1; |
| EXPECT_TRUE(render_pass0->backdrop_filter_bounds->isRRect( |
| &backdrop_filter_as_rect_0)); |
| EXPECT_TRUE(render_pass1->backdrop_filter_bounds->isRRect( |
| &backdrop_filter_as_rect_1)); |
| EXPECT_EQ(backdrop_filter_as_rect_0, backdrop_filter_as_rect_1); |
| EXPECT_EQ(backdrop_filter_as_rect_1.type(), SkRRect::kSimple_Type); |
| EXPECT_EQ(1.5f, backdrop_filter_as_rect_1.getSimpleRadii().x()); |
| EXPECT_EQ(SkRect::MakeXYWH(2.f, 3.f, 4.f, 5.f), |
| backdrop_filter_as_rect_1.rect()); |
| } |
| base::Value::Dict dict1 = CompositorRenderPassToDict(*render_pass1); |
| EXPECT_EQ(dict0, dict1); |
| } |
| |
| TEST(RenderPassIOTest, SharedQuadStateList) { |
| auto render_pass0 = CompositorRenderPass::Create(); |
| { |
| // Add two SQS. |
| SharedQuadState* sqs0 = render_pass0->CreateAndAppendSharedQuadState(); |
| ASSERT_TRUE(sqs0); |
| SharedQuadState* sqs1 = render_pass0->CreateAndAppendSharedQuadState(); |
| ASSERT_TRUE(sqs1); |
| gfx::Transform transform; |
| transform.MakeIdentity(); |
| gfx::LinearGradient gradient_mask(40); |
| gradient_mask.AddStep(/*fraction=*/0, /*alpha=*/0); |
| gradient_mask.AddStep(1, 255); |
| sqs1->SetAll( |
| transform, gfx::Rect(0, 0, 640, 480), gfx::Rect(10, 10, 600, 400), |
| gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(2.f, 3.f, 4.f, 5.f), 1.5f), |
| gradient_mask), |
| gfx::Rect(5, 20, 1000, 200), /*contents_opaque=*/false, |
| /*opacity_f=*/0.5f, SkBlendMode::kDstOver, /*sorting_context=*/101, |
| /*layer_id=*/0u, /*fast_rounded_corner=*/true); |
| } |
| base::Value::Dict dict0 = CompositorRenderPassToDict(*render_pass0); |
| auto render_pass1 = CompositorRenderPassFromDict(dict0); |
| ASSERT_TRUE(render_pass1); |
| { |
| // Verify two SQS. |
| EXPECT_EQ(2u, render_pass1->shared_quad_state_list.size()); |
| const SharedQuadState* sqs0 = |
| render_pass1->shared_quad_state_list.ElementAt(0); |
| EXPECT_TRUE(sqs0); |
| EXPECT_TRUE(sqs0->quad_to_target_transform.IsIdentity()); |
| EXPECT_EQ(gfx::Rect(), sqs0->quad_layer_rect); |
| EXPECT_EQ(gfx::Rect(), sqs0->visible_quad_layer_rect); |
| EXPECT_FALSE(sqs0->mask_filter_info.HasRoundedCorners()); |
| EXPECT_FALSE(sqs0->mask_filter_info.HasGradientMask()); |
| EXPECT_EQ(std::nullopt, sqs0->clip_rect); |
| EXPECT_TRUE(sqs0->are_contents_opaque); |
| EXPECT_EQ(1.0f, sqs0->opacity); |
| EXPECT_EQ(SkBlendMode::kSrcOver, sqs0->blend_mode); |
| EXPECT_EQ(0, sqs0->sorting_context_id); |
| EXPECT_FALSE(sqs0->is_fast_rounded_corner); |
| |
| const SharedQuadState* sqs1 = |
| render_pass1->shared_quad_state_list.ElementAt(1); |
| EXPECT_TRUE(sqs1); |
| EXPECT_TRUE(sqs1->quad_to_target_transform.IsIdentity()); |
| EXPECT_EQ(gfx::Rect(0, 0, 640, 480), sqs1->quad_layer_rect); |
| EXPECT_EQ(gfx::Rect(10, 10, 600, 400), sqs1->visible_quad_layer_rect); |
| EXPECT_EQ(gfx::RRectF::Type::kSingle, |
| sqs1->mask_filter_info.rounded_corner_bounds().GetType()); |
| EXPECT_EQ(1.5f, |
| sqs1->mask_filter_info.rounded_corner_bounds().GetSimpleRadius()); |
| ASSERT_TRUE(sqs1->mask_filter_info.HasGradientMask()); |
| EXPECT_EQ(40, sqs1->mask_filter_info.gradient_mask()->angle()); |
| EXPECT_EQ(2u, sqs1->mask_filter_info.gradient_mask()->step_count()); |
| EXPECT_EQ(gfx::LinearGradient::Step({0, 0}), |
| sqs1->mask_filter_info.gradient_mask()->steps()[0]); |
| EXPECT_EQ(gfx::LinearGradient::Step({1, 255}), |
| sqs1->mask_filter_info.gradient_mask()->steps()[1]); |
| EXPECT_EQ(gfx::RectF(2.f, 3.f, 4.f, 5.f), sqs1->mask_filter_info.bounds()); |
| EXPECT_EQ(gfx::Rect(5, 20, 1000, 200), sqs1->clip_rect); |
| EXPECT_FALSE(sqs1->are_contents_opaque); |
| EXPECT_EQ(0.5f, sqs1->opacity); |
| EXPECT_EQ(SkBlendMode::kDstOver, sqs1->blend_mode); |
| EXPECT_EQ(101, sqs1->sorting_context_id); |
| EXPECT_TRUE(sqs1->is_fast_rounded_corner); |
| } |
| base::Value::Dict dict1 = CompositorRenderPassToDict(*render_pass1); |
| EXPECT_EQ(dict0, dict1); |
| } |
| |
| TEST(RenderPassIOTest, QuadList) { |
| const size_t kSharedQuadStateCount = 3; |
| size_t quad_count = 0; |
| const std::array<DrawQuad::Material, 8> kQuadMaterials = { |
| DrawQuad::Material::kSolidColor, |
| DrawQuad::Material::kVideoHole, |
| DrawQuad::Material::kTextureContent, |
| DrawQuad::Material::kCompositorRenderPass, |
| DrawQuad::Material::kTiledContent, |
| DrawQuad::Material::kSurfaceContent, |
| DrawQuad::Material::kSurfaceContent, |
| }; |
| TestSurfaceIdAllocator kSurfaceId1(FrameSinkId(1, 1)); |
| TestSurfaceIdAllocator kSurfaceId2(FrameSinkId(2, 2)); |
| auto render_pass0 = CompositorRenderPass::Create(); |
| { |
| // Add to shared_quad_state_list. |
| for (size_t ii = 0; ii < kSharedQuadStateCount; ++ii) { |
| SharedQuadState* sqs = render_pass0->CreateAndAppendSharedQuadState(); |
| ASSERT_TRUE(sqs); |
| } |
| |
| size_t sqs_index = 0; |
| |
| // Add to quad_list. |
| { |
| // 1. SolidColorDrawQuad |
| SolidColorDrawQuad* quad = |
| render_pass0->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index), |
| gfx::Rect(0, 0, 30, 40), gfx::Rect(1, 2, 20, 30), true, |
| SkColors::kRed, false); |
| ++quad_count; |
| } |
| { |
| // 2. VideoHoleDrawQuad |
| VideoHoleDrawQuad* quad = |
| render_pass0->CreateAndAppendDrawQuad<VideoHoleDrawQuad>(); |
| quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index), |
| gfx::Rect(5, 5, 305, 405), gfx::Rect(15, 15, 205, 305), |
| false, base::UnguessableToken::Create()); |
| ++quad_count; |
| } |
| { |
| // 3. TextureDrawQuad |
| TextureDrawQuad* quad = |
| render_pass0->CreateAndAppendDrawQuad<TextureDrawQuad>(); |
| quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index), |
| gfx::Rect(0, 0, 100, 50), gfx::Rect(0, 0, 100, 50), false, |
| ResourceId(9u), gfx::PointF(0.f, 0.f), gfx::PointF(1.f, 1.f), |
| SkColors::kBlue, true, false, |
| gfx::ProtectedVideoType::kHardwareProtected); |
| |
| ++sqs_index; |
| ++quad_count; |
| } |
| { |
| // 4. CompositorRenderPassDrawQuad |
| CompositorRenderPassDrawQuad* quad = |
| render_pass0->CreateAndAppendDrawQuad<CompositorRenderPassDrawQuad>(); |
| quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index), |
| gfx::Rect(2, 3, 100, 50), gfx::Rect(2, 3, 100, 50), true, |
| CompositorRenderPassId{198u}, ResourceId(81u), |
| gfx::RectF(0.1f, 0.2f, 0.5f, 0.6f), gfx::Size(800, 600), |
| gfx::Vector2dF(1.1f, 0.9f), gfx::PointF(0.01f, 0.02f), |
| gfx::RectF(0.2f, 0.3f, 0.3f, 0.4f), true, 0.88f, true); |
| ++sqs_index; |
| ++quad_count; |
| } |
| { |
| // 5. TileDrawQuad |
| TileDrawQuad* quad = |
| render_pass0->CreateAndAppendDrawQuad<TileDrawQuad>(); |
| quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index), |
| gfx::Rect(0, 0, 256, 512), gfx::Rect(2, 2, 250, 500), true, |
| ResourceId(512u), gfx::RectF(0.0f, 0.0f, 0.9f, 0.8f), true, |
| true); |
| ++quad_count; |
| } |
| { |
| // 6. SurfaceDrawQuad |
| SurfaceDrawQuad* quad = |
| render_pass0->CreateAndAppendDrawQuad<SurfaceDrawQuad>(); |
| quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index), |
| gfx::Rect(0, 0, 512, 256), gfx::Rect(2, 2, 500, 250), true, |
| SurfaceRange(kSurfaceId1, kSurfaceId2), SkColors::kWhite, |
| false, false, true); |
| ++quad_count; |
| } |
| { |
| // 7. SurfaceDrawQuad with no starting SurfaceId |
| SurfaceDrawQuad* quad = |
| render_pass0->CreateAndAppendDrawQuad<SurfaceDrawQuad>(); |
| quad->SetAll(render_pass0->shared_quad_state_list.ElementAt(sqs_index), |
| gfx::Rect(10, 10, 512, 256), gfx::Rect(12, 12, 500, 250), |
| true, SurfaceRange(std::nullopt, kSurfaceId1), |
| SkColors::kBlack, true, true, false); |
| ++quad_count; |
| } |
| DCHECK_EQ(kSharedQuadStateCount, sqs_index + 1); |
| } |
| base::Value::Dict dict0 = CompositorRenderPassToDict(*render_pass0); |
| auto render_pass1 = CompositorRenderPassFromDict(dict0); |
| EXPECT_TRUE(render_pass1); |
| EXPECT_EQ(kSharedQuadStateCount, render_pass1->shared_quad_state_list.size()); |
| EXPECT_EQ(quad_count, render_pass1->quad_list.size()); |
| for (size_t ii = 0; ii < quad_count; ++ii) { |
| EXPECT_EQ(kQuadMaterials[ii], |
| render_pass1->quad_list.ElementAt(ii)->material); |
| } |
| base::Value::Dict dict1 = CompositorRenderPassToDict(*render_pass1); |
| EXPECT_EQ(dict0, dict1); |
| } |
| |
| TEST(RenderPassIOTest, CompositorRenderPassList) { |
| // Validate recorded render pass list data from https://www.espn.com/. |
| base::FilePath test_data_dir; |
| ASSERT_TRUE(base::PathService::Get(Paths::DIR_TEST_DATA, &test_data_dir)); |
| base::FilePath json_path = |
| test_data_dir.Append(FILE_PATH_LITERAL("render_pass_data")) |
| .Append(FILE_PATH_LITERAL("top_real_world_desktop")) |
| .Append(FILE_PATH_LITERAL("espn_2018")) |
| .Append(FILE_PATH_LITERAL("0463.json")); |
| ASSERT_TRUE(base::PathExists(json_path)); |
| std::string json_text; |
| ASSERT_TRUE(base::ReadFileToString(json_path, &json_text)); |
| |
| std::optional<base::Value> dict0 = |
| base::JSONReader::Read(json_text, base::JSON_PARSE_CHROMIUM_EXTENSIONS); |
| EXPECT_TRUE(dict0.has_value()); |
| CompositorRenderPassList render_pass_list; |
| EXPECT_TRUE( |
| CompositorRenderPassListFromDict(dict0->GetDict(), &render_pass_list)); |
| base::Value::Dict dict1 = CompositorRenderPassListToDict(render_pass_list); |
| // Since the test file doesn't contain the field |
| // 'intersects_damage_under' in its CompositorRenderPassDrawQuad, I'm |
| // removing the field on dict1 for the exact comparison to work. |
| base::Value::List* list = dict1.FindList("render_pass_list"); |
| for (auto& entry : *list) { |
| base::Value::List* quad_list = entry.GetDict().FindList("quad_list"); |
| |
| for (auto& quad_entry : *quad_list) { |
| if (base::Value* extra_value = |
| quad_entry.GetDict().Find("intersects_damage_under")) { |
| EXPECT_FALSE(extra_value->GetBool()); |
| ASSERT_TRUE(quad_entry.GetDict().Remove("intersects_damage_under")); |
| } |
| } |
| } |
| |
| EXPECT_EQ(dict0, dict1); |
| } |
| |
| TEST(RenderPassIOTest, CompositorFrameData) { |
| // Validate recorded multi-surface compositor frame data from a tab with |
| // https://www.youtube.com/ focused, and 4 other tabs in the background. |
| base::FilePath test_data_dir; |
| ASSERT_TRUE(base::PathService::Get(Paths::DIR_TEST_DATA, &test_data_dir)); |
| base::FilePath json_path = |
| test_data_dir.Append(FILE_PATH_LITERAL("render_pass_data")) |
| .Append(FILE_PATH_LITERAL("multi_surface_test")) |
| .Append(FILE_PATH_LITERAL("youtube_tab_focused")) |
| .Append(FILE_PATH_LITERAL("1641.json")); |
| ASSERT_TRUE(base::PathExists(json_path)); |
| std::string json_text; |
| ASSERT_TRUE(base::ReadFileToString(json_path, &json_text)); |
| |
| std::optional<base::Value> list0 = |
| base::JSONReader::Read(json_text, base::JSON_PARSE_CHROMIUM_EXTENSIONS); |
| EXPECT_TRUE(list0.has_value()); |
| std::vector<FrameData> frame_data_list; |
| EXPECT_TRUE(FrameDataFromList(list0->GetList(), &frame_data_list)); |
| base::Value::List list1 = FrameDataToList(frame_data_list); |
| |
| EXPECT_EQ(list0->GetList(), list1); |
| } |
| |
| } // namespace |
| } // namespace viz |