blob: 09dad0565d26a6574651729fc885959b0eec846b [file] [log] [blame]
// Copyright 2013 The Flutter 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 <unordered_map>
#include "flutter/display_list/dl_tile_mode.h"
#include "flutter/display_list/effects/dl_image_filter.h"
#include "flutter/display_list/geometry/dl_geometry_types.h"
#include "flutter/testing/testing.h"
#include "gtest/gtest.h"
#include "impeller/core/formats.h"
#include "impeller/core/texture_descriptor.h"
#include "impeller/display_list/aiks_unittests.h"
#include "impeller/display_list/canvas.h"
#include "impeller/geometry/geometry_asserts.h"
#include "impeller/renderer/render_target.h"
namespace impeller {
namespace testing {
std::unique_ptr<Canvas> CreateTestCanvas(
ContentContext& context,
std::optional<Rect> cull_rect = std::nullopt,
bool requires_readback = false) {
TextureDescriptor onscreen_desc;
onscreen_desc.size = {100, 100};
onscreen_desc.format =
context.GetDeviceCapabilities().GetDefaultColorFormat();
onscreen_desc.usage = TextureUsage::kRenderTarget;
onscreen_desc.storage_mode = StorageMode::kDevicePrivate;
onscreen_desc.sample_count = SampleCount::kCount1;
std::shared_ptr<Texture> onscreen =
context.GetContext()->GetResourceAllocator()->CreateTexture(
onscreen_desc);
ColorAttachment color0;
color0.load_action = LoadAction::kClear;
if (context.GetContext()->GetCapabilities()->SupportsOffscreenMSAA()) {
TextureDescriptor onscreen_msaa_desc = onscreen_desc;
onscreen_msaa_desc.sample_count = SampleCount::kCount4;
onscreen_msaa_desc.storage_mode = StorageMode::kDeviceTransient;
onscreen_msaa_desc.type = TextureType::kTexture2DMultisample;
std::shared_ptr<Texture> onscreen_msaa =
context.GetContext()->GetResourceAllocator()->CreateTexture(
onscreen_msaa_desc);
color0.resolve_texture = onscreen;
color0.texture = onscreen_msaa;
color0.store_action = StoreAction::kMultisampleResolve;
} else {
color0.texture = onscreen;
}
RenderTarget render_target;
render_target.SetColorAttachment(color0, 0);
if (cull_rect.has_value()) {
return std::make_unique<Canvas>(context, render_target, requires_readback,
cull_rect.value());
}
return std::make_unique<Canvas>(context, render_target, requires_readback);
}
TEST_P(AiksTest, TransformMultipliesCorrectly) {
ContentContext context(GetContext(), nullptr);
auto canvas = CreateTestCanvas(context);
ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(), Matrix());
// clang-format off
canvas->Translate(Vector3(100, 200));
ASSERT_MATRIX_NEAR(
canvas->GetCurrentTransform(),
Matrix( 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
100, 200, 0, 1));
canvas->Rotate(Radians(kPiOver2));
ASSERT_MATRIX_NEAR(
canvas->GetCurrentTransform(),
Matrix( 0, 1, 0, 0,
-1, 0, 0, 0,
0, 0, 1, 0,
100, 200, 0, 1));
canvas->Scale(Vector3(2, 3));
ASSERT_MATRIX_NEAR(
canvas->GetCurrentTransform(),
Matrix( 0, 2, 0, 0,
-3, 0, 0, 0,
0, 0, 0, 0,
100, 200, 0, 1));
canvas->Translate(Vector3(100, 200));
ASSERT_MATRIX_NEAR(
canvas->GetCurrentTransform(),
Matrix( 0, 2, 0, 0,
-3, 0, 0, 0,
0, 0, 0, 0,
-500, 400, 0, 1));
// clang-format on
}
TEST_P(AiksTest, CanvasCanPushPopCTM) {
ContentContext context(GetContext(), nullptr);
auto canvas = CreateTestCanvas(context);
ASSERT_EQ(canvas->GetSaveCount(), 1u);
ASSERT_EQ(canvas->Restore(), false);
canvas->Translate(Size{100, 100});
canvas->Save(10);
ASSERT_EQ(canvas->GetSaveCount(), 2u);
ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
Matrix::MakeTranslation({100.0, 100.0, 0.0}));
ASSERT_TRUE(canvas->Restore());
ASSERT_EQ(canvas->GetSaveCount(), 1u);
ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
Matrix::MakeTranslation({100.0, 100.0, 0.0}));
}
TEST_P(AiksTest, CanvasCTMCanBeUpdated) {
ContentContext context(GetContext(), nullptr);
auto canvas = CreateTestCanvas(context);
Matrix identity;
ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(), identity);
canvas->Translate(Size{100, 100});
ASSERT_MATRIX_NEAR(canvas->GetCurrentTransform(),
Matrix::MakeTranslation({100.0, 100.0, 0.0}));
}
TEST_P(AiksTest, BackdropCountDownNormal) {
ContentContext context(GetContext(), nullptr);
if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) {
GTEST_SKIP() << "Test requires device with framebuffer fetch";
}
auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
/*requires_readback=*/true);
// 3 backdrop filters
canvas->SetBackdropData({}, 3);
auto blur =
flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
flutter::DlRect rect = flutter::DlRect::MakeLTRB(0, 0, 50, 50);
EXPECT_TRUE(canvas->RequiresReadback());
canvas->DrawRect(rect, {.color = Color::Azure()});
canvas->SaveLayer({}, rect, blur.get(),
ContentBoundsPromise::kContainsContents,
/*total_content_depth=*/1);
canvas->Restore();
EXPECT_TRUE(canvas->RequiresReadback());
canvas->SaveLayer({}, rect, blur.get(),
ContentBoundsPromise::kContainsContents,
/*total_content_depth=*/1);
canvas->Restore();
EXPECT_TRUE(canvas->RequiresReadback());
canvas->SaveLayer({}, rect, blur.get(),
ContentBoundsPromise::kContainsContents,
/*total_content_depth=*/1);
canvas->Restore();
EXPECT_FALSE(canvas->RequiresReadback());
}
TEST_P(AiksTest, BackdropCountDownBackdropId) {
ContentContext context(GetContext(), nullptr);
if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) {
GTEST_SKIP() << "Test requires device with framebuffer fetch";
}
auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
/*requires_readback=*/true);
// 3 backdrop filters all with same id.
std::unordered_map<int64_t, BackdropData> data;
data[1] = BackdropData{.backdrop_count = 3};
canvas->SetBackdropData(data, 3);
auto blur =
flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
EXPECT_TRUE(canvas->RequiresReadback());
canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
{.color = Color::Azure()});
canvas->SaveLayer({}, std::nullopt, blur.get(),
ContentBoundsPromise::kContainsContents,
/*total_content_depth=*/1, /*can_distribute_opacity=*/false,
/*backdrop_id=*/1);
canvas->Restore();
EXPECT_FALSE(canvas->RequiresReadback());
canvas->SaveLayer({}, std::nullopt, blur.get(),
ContentBoundsPromise::kContainsContents,
/*total_content_depth=*/1, /*can_distribute_opacity=*/false,
/*backdrop_id=*/1);
canvas->Restore();
EXPECT_FALSE(canvas->RequiresReadback());
canvas->SaveLayer({}, std::nullopt, blur.get(),
ContentBoundsPromise::kContainsContents,
/*total_content_depth=*/1, /*can_distribute_opacity=*/false,
/*backdrop_id=*/1);
canvas->Restore();
EXPECT_FALSE(canvas->RequiresReadback());
}
TEST_P(AiksTest, BackdropCountDownBackdropIdMixed) {
ContentContext context(GetContext(), nullptr);
if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) {
GTEST_SKIP() << "Test requires device with framebuffer fetch";
}
auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
/*requires_readback=*/true);
// 3 backdrop filters, 2 with same id.
std::unordered_map<int64_t, BackdropData> data;
data[1] = BackdropData{.backdrop_count = 2};
canvas->SetBackdropData(data, 3);
auto blur =
flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
EXPECT_TRUE(canvas->RequiresReadback());
canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
{.color = Color::Azure()});
canvas->SaveLayer({}, std::nullopt, blur.get(),
ContentBoundsPromise::kContainsContents, 1, false);
canvas->Restore();
EXPECT_TRUE(canvas->RequiresReadback());
canvas->SaveLayer({}, std::nullopt, blur.get(),
ContentBoundsPromise::kContainsContents, 1, false, 1);
canvas->Restore();
EXPECT_FALSE(canvas->RequiresReadback());
canvas->SaveLayer({}, std::nullopt, blur.get(),
ContentBoundsPromise::kContainsContents, 1, false, 1);
canvas->Restore();
EXPECT_FALSE(canvas->RequiresReadback());
}
// We only know the total number of backdrop filters, not the number of backdrop
// filters in the root pass. If we reach a count of 0 while in a nested
// saveLayer, we should not restore to the onscreen.
TEST_P(AiksTest, BackdropCountDownWithNestedSaveLayers) {
ContentContext context(GetContext(), nullptr);
if (!context.GetDeviceCapabilities().SupportsFramebufferFetch()) {
GTEST_SKIP() << "Test requires device with framebuffer fetch";
}
auto canvas = CreateTestCanvas(context, Rect::MakeLTRB(0, 0, 100, 100),
/*requires_readback=*/true);
canvas->SetBackdropData({}, 2);
auto blur =
flutter::DlImageFilter::MakeBlur(4, 4, flutter::DlTileMode::kClamp);
EXPECT_TRUE(canvas->RequiresReadback());
canvas->DrawRect(flutter::DlRect::MakeLTRB(0, 0, 50, 50),
{.color = Color::Azure()});
canvas->SaveLayer({}, std::nullopt, blur.get(),
ContentBoundsPromise::kContainsContents,
/*total_content_depth=*/3);
// This filter is nested in the first saveLayer. We cannot restore to onscreen
// here.
canvas->SaveLayer({}, std::nullopt, blur.get(),
ContentBoundsPromise::kContainsContents,
/*total_content_depth=*/1);
canvas->Restore();
EXPECT_TRUE(canvas->RequiresReadback());
canvas->Restore();
EXPECT_TRUE(canvas->RequiresReadback());
}
} // namespace testing
} // namespace impeller