[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:
Jonah Williams
2025-04-02 13:29:14 -07:00
committed by GitHub
parent e971379436
commit 901b0f1afe
17 changed files with 504 additions and 58 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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, //

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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

View 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_

View File

@@ -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(

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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];

View File

@@ -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,