[Impeller] cache for text shadows. (#166228)
Fixes https://github.com/flutter/flutter/issues/164303 Fixes https://github.com/flutter/flutter/issues/165116 When rendering a text shadow, cache the resulting snapshot for at least one frame. Only attempts to content-aware hash for single glyphs (Icons). in other cases the exact layout may be different enough that its not worth trying to cache.
This commit is contained in:
@@ -51252,6 +51252,8 @@ ORIGIN: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.cc + .
|
||||
ORIGIN: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/contents/text_contents.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/contents/text_contents.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/contents/text_shadow_cache.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/contents/text_shadow_cache.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/contents/texture_contents.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/contents/texture_contents.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/contents/tiled_texture_contents.cc + ../../../flutter/LICENSE
|
||||
@@ -54240,6 +54242,8 @@ FILE: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.cc
|
||||
FILE: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.h
|
||||
FILE: ../../../flutter/impeller/entity/contents/text_contents.cc
|
||||
FILE: ../../../flutter/impeller/entity/contents/text_contents.h
|
||||
FILE: ../../../flutter/impeller/entity/contents/text_shadow_cache.cc
|
||||
FILE: ../../../flutter/impeller/entity/contents/text_shadow_cache.h
|
||||
FILE: ../../../flutter/impeller/entity/contents/texture_contents.cc
|
||||
FILE: ../../../flutter/impeller/entity/contents/texture_contents.h
|
||||
FILE: ../../../flutter/impeller/entity/contents/tiled_texture_contents.cc
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#include "flutter/fml/build_config.h"
|
||||
#include "flutter/impeller/display_list/aiks_unittests.h"
|
||||
#include "flutter/testing/testing.h"
|
||||
#include "impeller/display_list/aiks_context.h"
|
||||
#include "impeller/display_list/dl_dispatcher.h"
|
||||
#include "impeller/entity/contents/content_context.h"
|
||||
#include "impeller/entity/contents/text_contents.h"
|
||||
#include "impeller/entity/entity.h"
|
||||
#include "impeller/geometry/matrix.h"
|
||||
@@ -41,7 +44,8 @@ bool RenderTextInCanvasSkia(const std::shared_ptr<Context>& context,
|
||||
DisplayListBuilder& canvas,
|
||||
const std::string& text,
|
||||
const std::string_view& font_fixture,
|
||||
const TextRenderOptions& options = {}) {
|
||||
const TextRenderOptions& options = {},
|
||||
const std::optional<SkFont>& font = std::nullopt) {
|
||||
// Draw the baseline.
|
||||
DlPaint paint;
|
||||
paint.setColor(DlColor::kAqua().withAlpha(255 * 0.25));
|
||||
@@ -54,17 +58,23 @@ bool RenderTextInCanvasSkia(const std::shared_ptr<Context>& context,
|
||||
canvas.DrawCircle(options.position, 5.0, paint);
|
||||
|
||||
// Construct the text blob.
|
||||
auto c_font_fixture = std::string(font_fixture);
|
||||
auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
|
||||
if (!mapping) {
|
||||
return false;
|
||||
SkFont selected_font;
|
||||
if (!font.has_value()) {
|
||||
auto c_font_fixture = std::string(font_fixture);
|
||||
auto mapping =
|
||||
flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
|
||||
if (!mapping) {
|
||||
return false;
|
||||
}
|
||||
sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
|
||||
selected_font = SkFont(font_mgr->makeFromData(mapping), options.font_size);
|
||||
if (options.is_subpixel) {
|
||||
selected_font.setSubpixel(true);
|
||||
}
|
||||
} else {
|
||||
selected_font = font.value();
|
||||
}
|
||||
sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
|
||||
SkFont sk_font(font_mgr->makeFromData(mapping), options.font_size);
|
||||
if (options.is_subpixel) {
|
||||
sk_font.setSubpixel(true);
|
||||
}
|
||||
auto blob = SkTextBlob::MakeFromString(text.c_str(), sk_font);
|
||||
auto blob = SkTextBlob::MakeFromString(text.c_str(), selected_font);
|
||||
if (!blob) {
|
||||
return false;
|
||||
}
|
||||
@@ -731,5 +741,108 @@ TEST_P(AiksTest, TextContentsMismatchedTransformTest) {
|
||||
*render_pass));
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, TextWithShadowCache) {
|
||||
DisplayListBuilder builder;
|
||||
builder.Scale(GetContentScale().x, GetContentScale().y);
|
||||
DlPaint paint;
|
||||
paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
|
||||
builder.DrawPaint(paint);
|
||||
|
||||
AiksContext aiks_context(GetContext(),
|
||||
std::make_shared<TypographerContextSkia>());
|
||||
// Cache empty
|
||||
EXPECT_EQ(aiks_context.GetContentContext()
|
||||
.GetTextShadowCache()
|
||||
.GetCacheSizeForTesting(),
|
||||
0u);
|
||||
|
||||
ASSERT_TRUE(RenderTextInCanvasSkia(
|
||||
GetContext(), builder, "Hello World", kFontFixture,
|
||||
TextRenderOptions{
|
||||
.color = DlColor::kBlue(),
|
||||
.filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)}));
|
||||
|
||||
DisplayListToTexture(builder.Build(), {400, 400}, aiks_context);
|
||||
|
||||
// Text should be cached.
|
||||
EXPECT_EQ(aiks_context.GetContentContext()
|
||||
.GetTextShadowCache()
|
||||
.GetCacheSizeForTesting(),
|
||||
1u);
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, MultipleTextWithShadowCache) {
|
||||
DisplayListBuilder builder;
|
||||
builder.Scale(GetContentScale().x, GetContentScale().y);
|
||||
DlPaint paint;
|
||||
paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
|
||||
builder.DrawPaint(paint);
|
||||
|
||||
AiksContext aiks_context(GetContext(),
|
||||
std::make_shared<TypographerContextSkia>());
|
||||
// Cache empty
|
||||
EXPECT_EQ(aiks_context.GetContentContext()
|
||||
.GetTextShadowCache()
|
||||
.GetCacheSizeForTesting(),
|
||||
0u);
|
||||
|
||||
for (auto i = 0; i < 5; i++) {
|
||||
ASSERT_TRUE(RenderTextInCanvasSkia(
|
||||
GetContext(), builder, "Hello World", kFontFixture,
|
||||
TextRenderOptions{
|
||||
.color = DlColor::kBlue(),
|
||||
.filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)}));
|
||||
}
|
||||
|
||||
DisplayListToTexture(builder.Build(), {400, 400}, aiks_context);
|
||||
|
||||
// Text should be cached. Each text gets its own entry as we don't analyze the
|
||||
// strings.
|
||||
EXPECT_EQ(aiks_context.GetContentContext()
|
||||
.GetTextShadowCache()
|
||||
.GetCacheSizeForTesting(),
|
||||
5u);
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, SingleIconShadowTest) {
|
||||
DisplayListBuilder builder;
|
||||
builder.Scale(GetContentScale().x, GetContentScale().y);
|
||||
DlPaint paint;
|
||||
paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
|
||||
builder.DrawPaint(paint);
|
||||
|
||||
AiksContext aiks_context(GetContext(),
|
||||
std::make_shared<TypographerContextSkia>());
|
||||
// Cache empty
|
||||
EXPECT_EQ(aiks_context.GetContentContext()
|
||||
.GetTextShadowCache()
|
||||
.GetCacheSizeForTesting(),
|
||||
0u);
|
||||
|
||||
// Create font instance outside loop so all draws use identical font instance.
|
||||
auto c_font_fixture = std::string(kFontFixture);
|
||||
auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
|
||||
ASSERT_TRUE(mapping);
|
||||
sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
|
||||
SkFont sk_font(font_mgr->makeFromData(mapping), 50);
|
||||
|
||||
for (auto i = 0; i < 10; i++) {
|
||||
ASSERT_TRUE(RenderTextInCanvasSkia(
|
||||
GetContext(), builder, "A", kFontFixture,
|
||||
TextRenderOptions{
|
||||
.color = DlColor::kBlue(),
|
||||
.filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)},
|
||||
sk_font));
|
||||
}
|
||||
|
||||
DisplayListToTexture(builder.Build(), {400, 400}, aiks_context);
|
||||
|
||||
// Text should be cached. All 10 glyphs use the same cache entry.
|
||||
EXPECT_EQ(aiks_context.GetContentContext()
|
||||
.GetTextShadowCache()
|
||||
.GetCacheSizeForTesting(),
|
||||
1u);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
@@ -78,6 +78,7 @@ TEST_P(AiksTest, CollapsedDrawPaintInSubpassBackdropFilter) {
|
||||
|
||||
TEST_P(AiksTest, ColorMatrixFilterSubpassCollapseOptimization) {
|
||||
DisplayListBuilder builder(DlRect::MakeSize(GetWindowSize()));
|
||||
builder.DrawPaint(DlPaint().setColor(DlColor::kWhite()));
|
||||
|
||||
const float matrix[20] = {
|
||||
-1.0, 0, 0, 1.0, 0, //
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "impeller/base/validation.h"
|
||||
#include "impeller/core/formats.h"
|
||||
#include "impeller/display_list/color_filter.h"
|
||||
#include "impeller/display_list/dl_atlas_geometry.h"
|
||||
#include "impeller/display_list/image_filter.h"
|
||||
#include "impeller/display_list/skia_conversions.h"
|
||||
#include "impeller/entity/contents/atlas_contents.h"
|
||||
@@ -32,6 +31,7 @@
|
||||
#include "impeller/entity/contents/line_contents.h"
|
||||
#include "impeller/entity/contents/solid_rrect_blur_contents.h"
|
||||
#include "impeller/entity/contents/text_contents.h"
|
||||
#include "impeller/entity/contents/text_shadow_cache.h"
|
||||
#include "impeller/entity/contents/texture_contents.h"
|
||||
#include "impeller/entity/contents/vertices_contents.h"
|
||||
#include "impeller/entity/geometry/circle_geometry.h"
|
||||
@@ -1467,6 +1467,50 @@ bool Canvas::Restore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Canvas::AttemptBlurredTextOptimization(
|
||||
const std::shared_ptr<TextFrame>& text_frame,
|
||||
const std::shared_ptr<TextContents>& text_contents,
|
||||
Entity& entity,
|
||||
const Paint& paint) {
|
||||
if (!paint.mask_blur_descriptor.has_value() || //
|
||||
paint.image_filter != nullptr || //
|
||||
paint.color_filter != nullptr || //
|
||||
paint.invert_colors) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(bdero): This mask blur application is a hack. It will always wind up
|
||||
// doing a gaussian blur that affects the color source itself
|
||||
// instead of just the mask. The color filter text support
|
||||
// needs to be reworked in order to interact correctly with
|
||||
// mask filters.
|
||||
// https://github.com/flutter/flutter/issues/133297
|
||||
std::shared_ptr<FilterContents> filter =
|
||||
paint.mask_blur_descriptor->CreateMaskBlur(
|
||||
FilterInput::Make(text_contents),
|
||||
/*is_solid_color=*/true, GetCurrentTransform());
|
||||
|
||||
std::optional<Glyph> maybe_glyph = text_frame->AsSingleGlyph();
|
||||
int64_t identifier = maybe_glyph.has_value()
|
||||
? maybe_glyph.value().index
|
||||
: reinterpret_cast<int64_t>(text_frame.get());
|
||||
TextShadowCache::TextShadowCacheKey cache_key(
|
||||
/*p_max_basis=*/entity.GetTransform().GetMaxBasisLengthXY(),
|
||||
/*p_identifier=*/identifier,
|
||||
/*p_is_single_glyph=*/maybe_glyph.has_value(),
|
||||
/*p_font=*/text_frame->GetFont(),
|
||||
/*p_sigma=*/paint.mask_blur_descriptor->sigma);
|
||||
|
||||
std::optional<Entity> result = renderer_.GetTextShadowCache().Lookup(
|
||||
renderer_, entity, filter, cache_key);
|
||||
if (result.has_value()) {
|
||||
AddRenderEntityToCurrentPass(result.value(), /*reuse_depth=*/false);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::DrawTextFrame(const std::shared_ptr<TextFrame>& text_frame,
|
||||
Point position,
|
||||
const Paint& paint) {
|
||||
@@ -1491,15 +1535,12 @@ void Canvas::DrawTextFrame(const std::shared_ptr<TextFrame>& text_frame,
|
||||
entity.SetTransform(GetCurrentTransform() *
|
||||
Matrix::MakeTranslation(position));
|
||||
|
||||
// TODO(bdero): This mask blur application is a hack. It will always wind up
|
||||
// doing a gaussian blur that affects the color source itself
|
||||
// instead of just the mask. The color filter text support
|
||||
// needs to be reworked in order to interact correctly with
|
||||
// mask filters.
|
||||
// https://github.com/flutter/flutter/issues/133297
|
||||
entity.SetContents(paint.WithFilters(paint.WithMaskBlur(
|
||||
std::move(text_contents), true, GetCurrentTransform())));
|
||||
if (AttemptBlurredTextOptimization(text_frame, text_contents, entity,
|
||||
paint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity.SetContents(paint.WithFilters(std::move(text_contents)));
|
||||
AddRenderEntityToCurrentPass(entity, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "impeller/display_list/paint.h"
|
||||
#include "impeller/entity/contents/atlas_contents.h"
|
||||
#include "impeller/entity/contents/clip_contents.h"
|
||||
#include "impeller/entity/contents/text_contents.h"
|
||||
#include "impeller/entity/entity.h"
|
||||
#include "impeller/entity/entity_pass_clip_stack.h"
|
||||
#include "impeller/entity/geometry/geometry.h"
|
||||
@@ -368,6 +369,12 @@ class Canvas {
|
||||
const SamplerDescriptor& sampler,
|
||||
SourceRectConstraint src_rect_constraint);
|
||||
|
||||
bool AttemptBlurredTextOptimization(
|
||||
const std::shared_ptr<TextFrame>& text_frame,
|
||||
const std::shared_ptr<TextContents>& text_contents,
|
||||
Entity& entity,
|
||||
const Paint& paint);
|
||||
|
||||
RenderPass& GetCurrentRenderPass() const;
|
||||
|
||||
Canvas(const Canvas&) = delete;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "display_list/dl_sampling_options.h"
|
||||
#include "display_list/effects/dl_image_filter.h"
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "fml/closure.h"
|
||||
#include "impeller/core/formats.h"
|
||||
#include "impeller/display_list/aiks_context.h"
|
||||
#include "impeller/display_list/canvas.h"
|
||||
@@ -1333,15 +1334,19 @@ std::shared_ptr<Texture> DisplayListToTexture(
|
||||
);
|
||||
const auto& [data, count] = collector.TakeBackdropData();
|
||||
impeller_dispatcher.SetBackdropData(data, count);
|
||||
context.GetContentContext().GetTextShadowCache().MarkFrameStart();
|
||||
fml::ScopedCleanupClosure cleanup([&] {
|
||||
if (reset_host_buffer) {
|
||||
context.GetContentContext().GetTransientsBuffer().Reset();
|
||||
}
|
||||
context.GetContentContext().GetTextShadowCache().MarkFrameEnd();
|
||||
context.GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames();
|
||||
context.GetContext()->DisposeThreadLocalCachedResources();
|
||||
});
|
||||
|
||||
display_list->Dispatch(impeller_dispatcher, sk_cull_rect);
|
||||
impeller_dispatcher.FinishRecording();
|
||||
|
||||
if (reset_host_buffer) {
|
||||
context.GetContentContext().GetTransientsBuffer().Reset();
|
||||
}
|
||||
context.GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames();
|
||||
context.GetContext()->DisposeThreadLocalCachedResources();
|
||||
|
||||
return target.GetRenderTargetTexture();
|
||||
}
|
||||
|
||||
@@ -1366,11 +1371,16 @@ bool RenderToTarget(ContentContext& context,
|
||||
);
|
||||
const auto& [data, count] = collector.TakeBackdropData();
|
||||
impeller_dispatcher.SetBackdropData(data, count);
|
||||
context.GetTextShadowCache().MarkFrameStart();
|
||||
fml::ScopedCleanupClosure cleanup([&] {
|
||||
if (reset_host_buffer) {
|
||||
context.GetTransientsBuffer().Reset();
|
||||
}
|
||||
context.GetTextShadowCache().MarkFrameEnd();
|
||||
});
|
||||
|
||||
display_list->Dispatch(impeller_dispatcher, cull_rect);
|
||||
impeller_dispatcher.FinishRecording();
|
||||
if (reset_host_buffer) {
|
||||
context.GetTransientsBuffer().Reset();
|
||||
}
|
||||
context.GetLazyGlyphAtlas()->ResetTextFrames();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -179,6 +179,8 @@ impeller_component("entity") {
|
||||
"contents/sweep_gradient_contents.h",
|
||||
"contents/text_contents.cc",
|
||||
"contents/text_contents.h",
|
||||
"contents/text_shadow_cache.cc",
|
||||
"contents/text_shadow_cache.h",
|
||||
"contents/texture_contents.cc",
|
||||
"contents/texture_contents.h",
|
||||
"contents/tiled_texture_contents.cc",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "impeller/core/texture_descriptor.h"
|
||||
#include "impeller/entity/contents/framebuffer_blend_contents.h"
|
||||
#include "impeller/entity/contents/pipelines.h"
|
||||
#include "impeller/entity/contents/text_shadow_cache.h"
|
||||
#include "impeller/entity/entity.h"
|
||||
#include "impeller/entity/render_target_cache.h"
|
||||
#include "impeller/renderer/command_buffer.h"
|
||||
@@ -532,7 +533,8 @@ ContentContext::ContentContext(
|
||||
context_->GetResourceAllocator())
|
||||
: std::move(render_target_allocator)),
|
||||
host_buffer_(HostBuffer::Create(context_->GetResourceAllocator(),
|
||||
context_->GetIdleWaiter())) {
|
||||
context_->GetIdleWaiter())),
|
||||
text_shadow_cache_(std::make_unique<TextShadowCache>()) {
|
||||
if (!context_ || !context_->IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "impeller/base/validation.h"
|
||||
#include "impeller/core/formats.h"
|
||||
#include "impeller/core/host_buffer.h"
|
||||
#include "impeller/entity/contents/text_shadow_cache.h"
|
||||
#include "impeller/geometry/color.h"
|
||||
#include "impeller/renderer/capabilities.h"
|
||||
#include "impeller/renderer/command_buffer.h"
|
||||
@@ -294,6 +295,8 @@ class ContentContext {
|
||||
/// allocate their own device buffers.
|
||||
HostBuffer& GetTransientsBuffer() const { return *host_buffer_; }
|
||||
|
||||
TextShadowCache& GetTextShadowCache() const { return *text_shadow_cache_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Context> context_;
|
||||
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;
|
||||
@@ -340,6 +343,7 @@ class ContentContext {
|
||||
std::shared_ptr<RenderTargetAllocator> render_target_cache_;
|
||||
std::shared_ptr<HostBuffer> host_buffer_;
|
||||
std::shared_ptr<Texture> empty_texture_;
|
||||
std::unique_ptr<TextShadowCache> text_shadow_cache_;
|
||||
|
||||
ContentContext(const ContentContext&) = delete;
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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 "impeller/entity/contents/text_shadow_cache.h"
|
||||
|
||||
#include "fml/closure.h"
|
||||
#include "impeller/entity/contents/content_context.h"
|
||||
#include "impeller/entity/contents/contents.h"
|
||||
#include "impeller/entity/contents/filters/filter_contents.h"
|
||||
#include "impeller/geometry/sigma.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
// Rounds sigma values for gaussian blur to nearest decimal.
|
||||
static constexpr int32_t kMaxSigmaDenominator = 10;
|
||||
|
||||
TextShadowCache::TextShadowCacheKey::TextShadowCacheKey(Scalar p_max_basis,
|
||||
int64_t p_identifier,
|
||||
bool p_is_single_glyph,
|
||||
const Font& p_font,
|
||||
Sigma p_sigma)
|
||||
: max_basis(p_max_basis),
|
||||
identifier(p_identifier),
|
||||
is_single_glyph(p_is_single_glyph),
|
||||
font(p_font),
|
||||
rounded_sigma(Rational(std::round(p_sigma.sigma * kMaxSigmaDenominator),
|
||||
kMaxSigmaDenominator)) {}
|
||||
|
||||
void TextShadowCache::MarkFrameStart() {
|
||||
for (auto& entry : entries_) {
|
||||
entry.second.used_this_frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShadowCache::MarkFrameEnd() {
|
||||
absl::erase_if(entries_,
|
||||
[](const auto& pair) { return !pair.second.used_this_frame; });
|
||||
}
|
||||
|
||||
std::optional<Entity> TextShadowCache::Lookup(
|
||||
const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
const std::shared_ptr<FilterContents>& contents,
|
||||
const TextShadowCacheKey& text_key) {
|
||||
auto it = entries_.find(text_key);
|
||||
|
||||
if (it != entries_.end()) {
|
||||
it->second.used_this_frame = true;
|
||||
Entity cache_entity = it->second.entity.Clone();
|
||||
cache_entity.SetClipDepth(entity.GetClipDepth());
|
||||
cache_entity.SetTransform(entity.GetTransform() * it->second.key_matrix);
|
||||
return cache_entity;
|
||||
}
|
||||
|
||||
std::optional<Rect> filter_coverage = contents->GetCoverage(entity);
|
||||
if (!filter_coverage.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Execute the filter to produce a snapshot that can be resued on subsequent
|
||||
// frames. To prevent this texture from being re-used by the render target
|
||||
// cache, we temporarily disable any RT caching.
|
||||
renderer.GetRenderTargetCache()->DisableCache();
|
||||
fml::ScopedCleanupClosure closure(
|
||||
[&] { renderer.GetRenderTargetCache()->EnableCache(); });
|
||||
std::optional<Entity> maybe_entity =
|
||||
contents->GetEntity(renderer, entity, contents->GetCoverageHint());
|
||||
if (!maybe_entity.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// The original entity has a transform matrix A. The snapshot entity has a
|
||||
// transform matrix B. We need a function that converts A to B, so that if we
|
||||
// render an entity with a slightly different transform matrix A', it appears
|
||||
// in the correct position.
|
||||
// A * K = B
|
||||
// A-1 * A * K = A-1 * B
|
||||
// K = A-1 * B
|
||||
//
|
||||
// The transform matrix K can be computed by inverse A times B. Multiplying
|
||||
// any subsequent entity transforms by this matrix will correctly position
|
||||
// them.
|
||||
Matrix key_matrix =
|
||||
entity.GetTransform().Invert() * maybe_entity->GetTransform();
|
||||
entries_[text_key] =
|
||||
TextShadowCacheData{.entity = maybe_entity.value().Clone(),
|
||||
.used_this_frame = true,
|
||||
.key_matrix = key_matrix};
|
||||
|
||||
maybe_entity->SetClipDepth(entity.GetClipDepth());
|
||||
return maybe_entity;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
109
engine/src/flutter/impeller/entity/contents/text_shadow_cache.h
Normal file
109
engine/src/flutter/impeller/entity/contents/text_shadow_cache.h
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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.
|
||||
|
||||
#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_TEXT_SHADOW_CACHE_H_
|
||||
#define FLUTTER_IMPELLER_ENTITY_CONTENTS_TEXT_SHADOW_CACHE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/third_party/abseil-cpp/absl/container/flat_hash_map.h"
|
||||
#include "impeller/entity/entity.h"
|
||||
#include "impeller/geometry/scalar.h"
|
||||
#include "impeller/geometry/sigma.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
/// @brief A cache for blurred text that re-uses these across frames.
|
||||
///
|
||||
/// Text shadows are generally stable, but expensive to compute as we use a
|
||||
/// full gaussian blur. This class caches these shadows by text blob identifier
|
||||
/// and holds them for at least one frame.
|
||||
///
|
||||
/// Additionally, there is an optimization for a single glyph (generally an
|
||||
/// Icon) that uses the content itself as a key.
|
||||
///
|
||||
/// If there was a cheaper method of text frame identity, or a per-glyph caching
|
||||
/// system this could be more efficient. As it exists, this mostly ameliorate
|
||||
/// severe performance degradation for glyph shadows but does not provide
|
||||
/// substantially better performance than Skia.
|
||||
class TextShadowCache {
|
||||
public:
|
||||
TextShadowCache() = default;
|
||||
|
||||
~TextShadowCache() = default;
|
||||
|
||||
/// @brief A key to look up cached glyph textures.
|
||||
struct TextShadowCacheKey {
|
||||
Scalar max_basis;
|
||||
int64_t identifier;
|
||||
bool is_single_glyph;
|
||||
Font font;
|
||||
Rational rounded_sigma;
|
||||
|
||||
TextShadowCacheKey(Scalar p_max_basis,
|
||||
int64_t p_identifier,
|
||||
bool p_is_single_glyph,
|
||||
const Font& p_font,
|
||||
Sigma p_sigma);
|
||||
|
||||
struct Hash {
|
||||
std::size_t operator()(const TextShadowCacheKey& key) const {
|
||||
return fml::HashCombine(key.max_basis, key.identifier,
|
||||
key.is_single_glyph, key.font.GetHash(),
|
||||
key.rounded_sigma.GetHash());
|
||||
}
|
||||
};
|
||||
|
||||
struct Equal {
|
||||
constexpr bool operator()(const TextShadowCacheKey& lhs,
|
||||
const TextShadowCacheKey& rhs) const {
|
||||
return lhs.max_basis == rhs.max_basis &&
|
||||
lhs.identifier == rhs.identifier &&
|
||||
lhs.is_single_glyph == rhs.is_single_glyph &&
|
||||
lhs.font.IsEqual(rhs.font) &&
|
||||
lhs.rounded_sigma == rhs.rounded_sigma;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// @brief Mark all glyph textures as unused this frame.
|
||||
void MarkFrameStart();
|
||||
|
||||
/// @brief Remove all glyph textures that were not referenced at least once.
|
||||
void MarkFrameEnd();
|
||||
|
||||
/// @brief Lookup the entity in the cache with the given filter/text contents,
|
||||
/// returning the new entity to render.
|
||||
///
|
||||
/// If the entity is not present, render and place in the cache.
|
||||
std::optional<Entity> Lookup(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
const std::shared_ptr<FilterContents>& contents,
|
||||
const TextShadowCacheKey&);
|
||||
|
||||
// Visible for testing.
|
||||
size_t GetCacheSizeForTesting() const { return entries_.size(); }
|
||||
|
||||
private:
|
||||
TextShadowCache(const TextShadowCache&) = delete;
|
||||
|
||||
TextShadowCache& operator=(const TextShadowCache&) = delete;
|
||||
|
||||
struct TextShadowCacheData {
|
||||
Entity entity;
|
||||
bool used_this_frame = true;
|
||||
Matrix key_matrix;
|
||||
};
|
||||
|
||||
absl::flat_hash_map<TextShadowCacheKey,
|
||||
TextShadowCacheData,
|
||||
TextShadowCacheKey::Hash,
|
||||
TextShadowCacheKey::Equal>
|
||||
entries_;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_TEXT_SHADOW_CACHE_H_
|
||||
@@ -14,12 +14,14 @@ RenderTargetCache::RenderTargetCache(std::shared_ptr<Allocator> allocator,
|
||||
keep_alive_frame_count_(keep_alive_frame_count) {}
|
||||
|
||||
void RenderTargetCache::Start() {
|
||||
cache_disabled_count_ = 0;
|
||||
for (auto& td : render_target_data_) {
|
||||
td.used_this_frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderTargetCache::End() {
|
||||
cache_disabled_count_ = 0;
|
||||
std::vector<RenderTargetData> retain;
|
||||
|
||||
for (RenderTargetData& td : render_target_data_) {
|
||||
@@ -33,6 +35,22 @@ void RenderTargetCache::End() {
|
||||
render_target_data_.swap(retain);
|
||||
}
|
||||
|
||||
void RenderTargetCache::DisableCache() {
|
||||
cache_disabled_count_++;
|
||||
}
|
||||
|
||||
bool RenderTargetCache::CacheEnabled() const {
|
||||
return cache_disabled_count_ == 0;
|
||||
}
|
||||
|
||||
void RenderTargetCache::EnableCache() {
|
||||
FML_DCHECK(cache_disabled_count_ > 0);
|
||||
if (cache_disabled_count_ == 0) {
|
||||
return;
|
||||
}
|
||||
cache_disabled_count_--;
|
||||
}
|
||||
|
||||
RenderTarget RenderTargetCache::CreateOffscreen(
|
||||
const Context& context,
|
||||
ISize size,
|
||||
@@ -54,19 +72,22 @@ RenderTarget RenderTargetCache::CreateOffscreen(
|
||||
.has_msaa = false,
|
||||
.has_depth_stencil = stencil_attachment_config.has_value(),
|
||||
};
|
||||
for (RenderTargetData& render_target_data : render_target_data_) {
|
||||
const RenderTargetConfig other_config = render_target_data.config;
|
||||
if (!render_target_data.used_this_frame && other_config == config) {
|
||||
render_target_data.used_this_frame = true;
|
||||
render_target_data.keep_alive_frame_count = keep_alive_frame_count_;
|
||||
ColorAttachment color0 =
|
||||
render_target_data.render_target.GetColorAttachment(0);
|
||||
std::optional<DepthAttachment> depth =
|
||||
render_target_data.render_target.GetDepthAttachment();
|
||||
std::shared_ptr<Texture> depth_tex = depth ? depth->texture : nullptr;
|
||||
return RenderTargetAllocator::CreateOffscreen(
|
||||
context, size, mip_count, label, color_attachment_config,
|
||||
stencil_attachment_config, color0.texture, depth_tex);
|
||||
|
||||
if (CacheEnabled()) {
|
||||
for (RenderTargetData& render_target_data : render_target_data_) {
|
||||
const RenderTargetConfig other_config = render_target_data.config;
|
||||
if (!render_target_data.used_this_frame && other_config == config) {
|
||||
render_target_data.used_this_frame = true;
|
||||
render_target_data.keep_alive_frame_count = keep_alive_frame_count_;
|
||||
ColorAttachment color0 =
|
||||
render_target_data.render_target.GetColorAttachment(0);
|
||||
std::optional<DepthAttachment> depth =
|
||||
render_target_data.render_target.GetDepthAttachment();
|
||||
std::shared_ptr<Texture> depth_tex = depth ? depth->texture : nullptr;
|
||||
return RenderTargetAllocator::CreateOffscreen(
|
||||
context, size, mip_count, label, color_attachment_config,
|
||||
stencil_attachment_config, color0.texture, depth_tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
RenderTarget created_target = RenderTargetAllocator::CreateOffscreen(
|
||||
@@ -107,20 +128,22 @@ RenderTarget RenderTargetCache::CreateOffscreenMSAA(
|
||||
.has_msaa = true,
|
||||
.has_depth_stencil = stencil_attachment_config.has_value(),
|
||||
};
|
||||
for (RenderTargetData& render_target_data : render_target_data_) {
|
||||
const RenderTargetConfig other_config = render_target_data.config;
|
||||
if (!render_target_data.used_this_frame && other_config == config) {
|
||||
render_target_data.used_this_frame = true;
|
||||
render_target_data.keep_alive_frame_count = keep_alive_frame_count_;
|
||||
ColorAttachment color0 =
|
||||
render_target_data.render_target.GetColorAttachment(0);
|
||||
std::optional<DepthAttachment> depth =
|
||||
render_target_data.render_target.GetDepthAttachment();
|
||||
std::shared_ptr<Texture> depth_tex = depth ? depth->texture : nullptr;
|
||||
return RenderTargetAllocator::CreateOffscreenMSAA(
|
||||
context, size, mip_count, label, color_attachment_config,
|
||||
stencil_attachment_config, color0.texture, color0.resolve_texture,
|
||||
depth_tex);
|
||||
if (CacheEnabled()) {
|
||||
for (RenderTargetData& render_target_data : render_target_data_) {
|
||||
const RenderTargetConfig other_config = render_target_data.config;
|
||||
if (!render_target_data.used_this_frame && other_config == config) {
|
||||
render_target_data.used_this_frame = true;
|
||||
render_target_data.keep_alive_frame_count = keep_alive_frame_count_;
|
||||
ColorAttachment color0 =
|
||||
render_target_data.render_target.GetColorAttachment(0);
|
||||
std::optional<DepthAttachment> depth =
|
||||
render_target_data.render_target.GetDepthAttachment();
|
||||
std::shared_ptr<Texture> depth_tex = depth ? depth->texture : nullptr;
|
||||
return RenderTargetAllocator::CreateOffscreenMSAA(
|
||||
context, size, mip_count, label, color_attachment_config,
|
||||
stencil_attachment_config, color0.texture, color0.resolve_texture,
|
||||
depth_tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
RenderTarget created_target = RenderTargetAllocator::CreateOffscreenMSAA(
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define FLUTTER_IMPELLER_ENTITY_RENDER_TARGET_CACHE_H_
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "impeller/renderer/render_target.h"
|
||||
|
||||
namespace impeller {
|
||||
@@ -27,6 +28,12 @@ class RenderTargetCache : public RenderTargetAllocator {
|
||||
// |RenderTargetAllocator|
|
||||
void End() override;
|
||||
|
||||
// |RenderTargetAllocator|
|
||||
void DisableCache() override;
|
||||
|
||||
// |RenderTargetAllocator|
|
||||
void EnableCache() override;
|
||||
|
||||
RenderTarget CreateOffscreen(
|
||||
const Context& context,
|
||||
ISize size,
|
||||
@@ -65,8 +72,11 @@ class RenderTargetCache : public RenderTargetAllocator {
|
||||
RenderTarget render_target;
|
||||
};
|
||||
|
||||
bool CacheEnabled() const;
|
||||
|
||||
std::vector<RenderTargetData> render_target_data_;
|
||||
uint32_t keep_alive_frame_count_;
|
||||
uint32_t cache_disabled_count_ = 0;
|
||||
|
||||
RenderTargetCache(const RenderTargetCache&) = delete;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace impeller {
|
||||
/// quality blurs (with exponentially diminishing returns for the same sigma
|
||||
/// input). Making this value any lower results in a noticable loss of
|
||||
/// quality in the blur.
|
||||
constexpr static float kKernelRadiusPerSigma = 1.73205080757;
|
||||
constexpr static float kKernelRadiusPerSigma = 1.73205080757f;
|
||||
|
||||
struct Radius;
|
||||
|
||||
|
||||
@@ -177,6 +177,12 @@ class RenderTargetAllocator {
|
||||
const std::shared_ptr<Texture>& existing_color_resolve_texture = nullptr,
|
||||
const std::shared_ptr<Texture>& existing_depth_stencil_texture = nullptr);
|
||||
|
||||
/// @brief Disable any caching until the next call to `EnabledCache`.
|
||||
virtual void DisableCache() {}
|
||||
|
||||
/// @brief Re-enable any caching if disabled.
|
||||
virtual void EnableCache() {}
|
||||
|
||||
/// @brief Mark the beginning of a frame workload.
|
||||
///
|
||||
/// This may be used to reset any tracking state on whether or not a
|
||||
|
||||
@@ -146,6 +146,17 @@ bool TextFrame::IsFrameComplete() const {
|
||||
return bound_values_.size() == run_size;
|
||||
}
|
||||
|
||||
const Font& TextFrame::GetFont() const {
|
||||
return runs_[0].GetFont();
|
||||
}
|
||||
|
||||
std::optional<Glyph> TextFrame::AsSingleGlyph() const {
|
||||
if (runs_.size() == 1 && runs_[0].GetGlyphCount() == 1) {
|
||||
return runs_[0].GetGlyphPositions()[0].glyph;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const FrameBounds& TextFrame::GetFrameBounds(size_t index) const {
|
||||
FML_DCHECK(index < bound_values_.size());
|
||||
return bound_values_[index];
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include "impeller/geometry/rational.h"
|
||||
#include "impeller/typographer/glyph.h"
|
||||
#include "impeller/typographer/glyph_atlas.h"
|
||||
#include "impeller/typographer/text_run.h"
|
||||
|
||||
@@ -80,6 +81,13 @@ class TextFrame {
|
||||
/// This method is only valid if [IsFrameComplete] returns true.
|
||||
const FrameBounds& GetFrameBounds(size_t index) const;
|
||||
|
||||
/// @brief If this text frame contains a single glyph (such as for an Icon),
|
||||
/// then return it, otherwise std::nullopt.
|
||||
std::optional<Glyph> AsSingleGlyph() const;
|
||||
|
||||
/// @brief Return the font of the first glyph run.
|
||||
const Font& GetFont() const;
|
||||
|
||||
/// @brief Store text frame scale, offset, and properties for hashing in th
|
||||
/// glyph atlas.
|
||||
void SetPerFrameData(Rational scale,
|
||||
|
||||
Reference in New Issue
Block a user