[Impeller]: new blur - adds mips for backdrop filters (#49607)
If the new blur flag is on and a blur is used as a backdrop filter, the
rendered texture will now have mipmaps which will make the downscaling
step of the blur have more signal (and thus be less shimmery).
issue: https://github.com/flutter/flutter/issues/140193
fixes: https://github.com/flutter/flutter/issues/140949
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making or feature I am
adding, or the PR is [test-exempt]. See [testing the engine] for
instructions on writing and running engine tests.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I signed the [CLA].
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index fb771b8..df2bfd9 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -5553,6 +5553,8 @@
ORIGIN: ../../../flutter/impeller/renderer/stroke.comp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/surface.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/surface.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/texture_mipmap.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/texture_mipmap.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/threadgroup_sizing_test.comp + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/vertex_buffer_builder.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/vertex_buffer_builder.h + ../../../flutter/LICENSE
@@ -8381,6 +8383,8 @@
FILE: ../../../flutter/impeller/renderer/stroke.comp
FILE: ../../../flutter/impeller/renderer/surface.cc
FILE: ../../../flutter/impeller/renderer/surface.h
+FILE: ../../../flutter/impeller/renderer/texture_mipmap.cc
+FILE: ../../../flutter/impeller/renderer/texture_mipmap.h
FILE: ../../../flutter/impeller/renderer/threadgroup_sizing_test.comp
FILE: ../../../flutter/impeller/renderer/vertex_buffer_builder.cc
FILE: ../../../flutter/impeller/renderer/vertex_buffer_builder.h
diff --git a/impeller/aiks/aiks_context.cc b/impeller/aiks/aiks_context.cc
index d001c18..3be53f9 100644
--- a/impeller/aiks/aiks_context.cc
+++ b/impeller/aiks/aiks_context.cc
@@ -12,14 +12,18 @@
AiksContext::AiksContext(
std::shared_ptr<Context> context,
- std::shared_ptr<TypographerContext> typographer_context)
+ std::shared_ptr<TypographerContext> typographer_context,
+ std::optional<std::shared_ptr<RenderTargetAllocator>>
+ render_target_allocator)
: context_(std::move(context)) {
if (!context_ || !context_->IsValid()) {
return;
}
content_context_ = std::make_unique<ContentContext>(
- context_, std::move(typographer_context));
+ context_, std::move(typographer_context),
+ render_target_allocator.has_value() ? render_target_allocator.value()
+ : nullptr);
if (!content_context_->IsValid()) {
return;
}
diff --git a/impeller/aiks/aiks_context.h b/impeller/aiks/aiks_context.h
index ad8d642..5b35b63 100644
--- a/impeller/aiks/aiks_context.h
+++ b/impeller/aiks/aiks_context.h
@@ -28,8 +28,12 @@
/// `nullptr` is supplied, then attempting to draw
/// text with Aiks will result in validation
/// errors.
+ /// @param render_target_allocator Injects a render target allocator or
+ /// allocates its own if none is supplied.
AiksContext(std::shared_ptr<Context> context,
- std::shared_ptr<TypographerContext> typographer_context);
+ std::shared_ptr<TypographerContext> typographer_context,
+ std::optional<std::shared_ptr<RenderTargetAllocator>>
+ render_target_allocator = std::nullopt);
~AiksContext();
diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc
index e1f75aa..c2b0739 100644
--- a/impeller/aiks/aiks_unittests.cc
+++ b/impeller/aiks/aiks_unittests.cc
@@ -4,6 +4,7 @@
#include "flutter/impeller/aiks/aiks_unittests.h"
+#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
@@ -26,6 +27,7 @@
#include "impeller/entity/contents/radial_gradient_contents.h"
#include "impeller/entity/contents/solid_color_contents.h"
#include "impeller/entity/contents/sweep_gradient_contents.h"
+#include "impeller/entity/render_target_cache.h"
#include "impeller/geometry/color.h"
#include "impeller/geometry/constants.h"
#include "impeller/geometry/geometry_asserts.h"
@@ -3716,5 +3718,81 @@
// will be filled with NaNs and may produce a magenta texture on macOS or iOS.
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
+
+TEST_P(AiksTest, GuassianBlurUpdatesMipmapContents) {
+ // This makes sure if mip maps are recycled across invocations of blurs the
+ // contents get updated each frame correctly. If they aren't updated the color
+ // inside the blur and outside the blur will be different.
+ //
+ // If there is some change to render target caching this could display a false
+ // positive in the future. Also, if the LOD that is rendered is 1 it could
+ // present a false positive.
+ int32_t count = 0;
+ auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
+ Canvas canvas;
+ if (count++ == 0) {
+ canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
+ } else {
+ canvas.DrawCircle({100, 100}, 50, {.color = Color::Chartreuse()});
+ }
+ canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20});
+ canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
+ ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
+ FilterContents::BlurStyle::kNormal,
+ Entity::TileMode::kClamp));
+ canvas.Restore();
+ return canvas.EndRecordingAsPicture();
+ };
+
+ ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
+TEST_P(AiksTest, GaussianBlurSetsMipCountOnPass) {
+ Canvas canvas;
+ canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
+ canvas.SaveLayer({}, std::nullopt,
+ ImageFilter::MakeBlur(Sigma(3), Sigma(3),
+ FilterContents::BlurStyle::kNormal,
+ Entity::TileMode::kClamp));
+ canvas.Restore();
+
+ Picture picture = canvas.EndRecordingAsPicture();
+
+ int32_t max_mip_count = 0;
+ picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool {
+ if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
+ max_mip_count =
+ std::max(max_mip_count, subpass->get()->GetRequiredMipCount());
+ }
+ return true;
+ });
+
+ EXPECT_EQ(1, max_mip_count);
+}
+
+TEST_P(AiksTest, GaussianBlurAllocatesCorrectMipCountRenderTarget) {
+ Canvas canvas;
+ canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
+ canvas.SaveLayer({}, std::nullopt,
+ ImageFilter::MakeBlur(Sigma(3), Sigma(3),
+ FilterContents::BlurStyle::kNormal,
+ Entity::TileMode::kClamp));
+ canvas.Restore();
+
+ Picture picture = canvas.EndRecordingAsPicture();
+ std::shared_ptr<RenderTargetCache> cache =
+ std::make_shared<RenderTargetCache>(GetContext()->GetResourceAllocator());
+ AiksContext aiks_context(GetContext(), nullptr, cache);
+ picture.ToImage(aiks_context, {100, 100});
+
+ size_t max_mip_count = 0;
+ for (auto it = cache->GetTextureDataBegin(); it != cache->GetTextureDataEnd();
+ ++it) {
+ max_mip_count =
+ std::max(it->texture->GetTextureDescriptor().mip_count, max_mip_count);
+ }
+ EXPECT_EQ(max_mip_count, 1lu);
+}
+
} // namespace testing
} // namespace impeller
diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc
index c5b9ae9..f66da9a 100644
--- a/impeller/aiks/canvas.cc
+++ b/impeller/aiks/canvas.cc
@@ -133,6 +133,37 @@
Save(false);
}
+namespace {
+class MipCountVisitor : public ImageFilterVisitor {
+ public:
+ virtual void Visit(const BlurImageFilter& filter) {
+ required_mip_count_ = FilterContents::kBlurFilterRequiredMipCount;
+ }
+ virtual void Visit(const LocalMatrixImageFilter& filter) {
+ required_mip_count_ = 1;
+ }
+ virtual void Visit(const DilateImageFilter& filter) {
+ required_mip_count_ = 1;
+ }
+ virtual void Visit(const ErodeImageFilter& filter) {
+ required_mip_count_ = 1;
+ }
+ virtual void Visit(const MatrixImageFilter& filter) {
+ required_mip_count_ = 1;
+ }
+ virtual void Visit(const ComposeImageFilter& filter) {
+ required_mip_count_ = 1;
+ }
+ virtual void Visit(const ColorImageFilter& filter) {
+ required_mip_count_ = 1;
+ }
+ int32_t GetRequiredMipCount() const { return required_mip_count_; }
+
+ private:
+ int32_t required_mip_count_ = -1;
+};
+} // namespace
+
void Canvas::Save(bool create_subpass,
BlendMode blend_mode,
const std::shared_ptr<ImageFilter>& backdrop_filter) {
@@ -156,6 +187,9 @@
return filter;
};
subpass->SetBackdropFilter(backdrop_filter_proc);
+ MipCountVisitor mip_count_visitor;
+ backdrop_filter->Visit(mip_count_visitor);
+ subpass->SetRequiredMipCount(mip_count_visitor.GetRequiredMipCount());
}
subpass->SetBlendMode(blend_mode);
current_pass_ = GetCurrentPass().AddSubpass(std::move(subpass));
diff --git a/impeller/aiks/picture.cc b/impeller/aiks/picture.cc
index ea07dfd..8fc31dc 100644
--- a/impeller/aiks/picture.cc
+++ b/impeller/aiks/picture.cc
@@ -64,6 +64,7 @@
*impeller_context, // context
render_target_allocator, // allocator
size, // size
+ /*mip_count=*/1,
"Picture Snapshot MSAA", // label
RenderTarget::
kDefaultColorAttachmentConfigMSAA, // color_attachment_config
@@ -71,9 +72,10 @@
);
} else {
target = RenderTarget::CreateOffscreen(
- *impeller_context, // context
- render_target_allocator, // allocator
- size, // size
+ *impeller_context, // context
+ render_target_allocator, // allocator
+ size, // size
+ /*mip_count=*/1,
"Picture Snapshot", // label
RenderTarget::kDefaultColorAttachmentConfig, // color_attachment_config
std::nullopt // stencil_attachment_config
diff --git a/impeller/core/texture.h b/impeller/core/texture.h
index b28dbb2..af14a9e 100644
--- a/impeller/core/texture.h
+++ b/impeller/core/texture.h
@@ -45,6 +45,9 @@
virtual Scalar GetYCoordScale() const;
+ /// Returns true if mipmaps have never been generated.
+ /// The contents of the mipmap may be out of date if the root texture has been
+ /// modified and the mipmaps hasn't been regenerated.
bool NeedsMipmapGeneration() const;
protected:
diff --git a/impeller/entity/contents/checkerboard_contents_unittests.cc b/impeller/entity/contents/checkerboard_contents_unittests.cc
index 4632862..63a2cbd 100644
--- a/impeller/entity/contents/checkerboard_contents_unittests.cc
+++ b/impeller/entity/contents/checkerboard_contents_unittests.cc
@@ -35,7 +35,8 @@
auto buffer = content_context->GetContext()->CreateCommandBuffer();
auto render_target = RenderTarget::CreateOffscreenMSAA(
*content_context->GetContext(),
- *GetContentContext()->GetRenderTargetCache(), {100, 100});
+ *GetContentContext()->GetRenderTargetCache(), {100, 100},
+ /*mip_count=*/1);
auto render_pass = buffer->CreateRenderPass(render_target);
Entity entity;
diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc
index 634ae0b..7680481 100644
--- a/impeller/entity/contents/content_context.cc
+++ b/impeller/entity/contents/content_context.cc
@@ -418,14 +418,14 @@
RenderTarget subpass_target;
if (context->GetCapabilities()->SupportsOffscreenMSAA() && msaa_enabled) {
subpass_target = RenderTarget::CreateOffscreenMSAA(
- *context, *GetRenderTargetCache(), texture_size,
+ *context, *GetRenderTargetCache(), texture_size, /*mip_count=*/1,
SPrintF("%s Offscreen", label.c_str()),
RenderTarget::kDefaultColorAttachmentConfigMSAA,
std::nullopt // stencil_attachment_config
);
} else {
subpass_target = RenderTarget::CreateOffscreen(
- *context, *GetRenderTargetCache(), texture_size,
+ *context, *GetRenderTargetCache(), texture_size, /*mip_count=*/1,
SPrintF("%s Offscreen", label.c_str()),
RenderTarget::kDefaultColorAttachmentConfig, //
std::nullopt // stencil_attachment_config
diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc
index 5f2c310..235a531 100644
--- a/impeller/entity/contents/filters/filter_contents.cc
+++ b/impeller/entity/contents/filters/filter_contents.cc
@@ -50,6 +50,12 @@
return blur;
}
+#ifdef IMPELLER_ENABLE_NEW_GAUSSIAN_FILTER
+const int32_t FilterContents::kBlurFilterRequiredMipCount = 4;
+#else
+const int32_t FilterContents::kBlurFilterRequiredMipCount = 1;
+#endif
+
std::shared_ptr<FilterContents> FilterContents::MakeGaussianBlur(
const FilterInput::Ref& input,
Sigma sigma_x,
diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h
index 3e210d8..076593f 100644
--- a/impeller/entity/contents/filters/filter_contents.h
+++ b/impeller/entity/contents/filters/filter_contents.h
@@ -20,6 +20,8 @@
class FilterContents : public Contents {
public:
+ static const int32_t kBlurFilterRequiredMipCount;
+
enum class BlurStyle {
/// Blurred inside and outside.
kNormal,
diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
index 22d49a3..13610bd 100644
--- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
+++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
@@ -11,6 +11,7 @@
#include "impeller/entity/texture_fill.vert.h"
#include "impeller/renderer/command.h"
#include "impeller/renderer/render_pass.h"
+#include "impeller/renderer/texture_mipmap.h"
#include "impeller/renderer/vertex_buffer_builder.h"
namespace impeller {
@@ -186,7 +187,6 @@
rect.GetSize());
return result.Scale(1.0f / Vector2(reference.GetSize()));
}
-
} // namespace
GaussianBlurFilterContents::GaussianBlurFilterContents(
@@ -278,6 +278,12 @@
entity.GetClipDepth()); // No blur to render.
}
+ // In order to avoid shimmering in downsampling step, we should have mips.
+ if (input_snapshot->texture->GetMipCount() <= 1) {
+ FML_DLOG(ERROR) << "Applying gaussian blur without mipmap.";
+ }
+ FML_DCHECK(!input_snapshot->texture->NeedsMipmapGeneration());
+
Scalar desired_scalar =
std::min(CalculateScale(scaled_sigma.x), CalculateScale(scaled_sigma.y));
// TODO(jonahwilliams): If desired_scalar is 1.0 and we fully acquired the
diff --git a/impeller/entity/contents/scene_contents.cc b/impeller/entity/contents/scene_contents.cc
index 54eb9f5..f5da628 100644
--- a/impeller/entity/contents/scene_contents.cc
+++ b/impeller/entity/contents/scene_contents.cc
@@ -50,7 +50,8 @@
*renderer.GetContext(), // context
*renderer.GetRenderTargetCache(), // allocator
ISize(coverage.value().GetSize()), // size
- "SceneContents", // label
+ /*mip_count=*/1,
+ "SceneContents", // label
RenderTarget::AttachmentConfigMSAA{
.storage_mode = StorageMode::kDeviceTransient,
.resolve_storage_mode = StorageMode::kDevicePrivate,
@@ -68,7 +69,8 @@
*renderer.GetContext(), // context
*renderer.GetRenderTargetCache(), // allocator
ISize(coverage.value().GetSize()), // size
- "SceneContents", // label
+ /*mip_count=*/1,
+ "SceneContents", // label
RenderTarget::AttachmentConfig{
.storage_mode = StorageMode::kDevicePrivate,
.load_action = LoadAction::kClear,
diff --git a/impeller/entity/contents/tiled_texture_contents_unittests.cc b/impeller/entity/contents/tiled_texture_contents_unittests.cc
index 2060f23..4715eaa 100644
--- a/impeller/entity/contents/tiled_texture_contents_unittests.cc
+++ b/impeller/entity/contents/tiled_texture_contents_unittests.cc
@@ -33,7 +33,8 @@
auto buffer = content_context->GetContext()->CreateCommandBuffer();
auto render_target = RenderTarget::CreateOffscreenMSAA(
*content_context->GetContext(),
- *GetContentContext()->GetRenderTargetCache(), {100, 100});
+ *GetContentContext()->GetRenderTargetCache(), {100, 100},
+ /*mip_count=*/1);
auto render_pass = buffer->CreateRenderPass(render_target);
ASSERT_TRUE(contents.Render(*GetContentContext(), {}, *render_pass));
@@ -68,7 +69,8 @@
auto buffer = content_context->GetContext()->CreateCommandBuffer();
auto render_target = RenderTarget::CreateOffscreenMSAA(
*content_context->GetContext(),
- *GetContentContext()->GetRenderTargetCache(), {100, 100});
+ *GetContentContext()->GetRenderTargetCache(), {100, 100},
+ /*mip_count=*/1);
auto render_pass = buffer->CreateRenderPass(render_target);
ASSERT_TRUE(contents.Render(*GetContentContext(), {}, *render_pass));
diff --git a/impeller/entity/contents/vertices_contents_unittests.cc b/impeller/entity/contents/vertices_contents_unittests.cc
index 173b13e..17fac72 100644
--- a/impeller/entity/contents/vertices_contents_unittests.cc
+++ b/impeller/entity/contents/vertices_contents_unittests.cc
@@ -61,7 +61,8 @@
auto buffer = content_context->GetContext()->CreateCommandBuffer();
auto render_target = RenderTarget::CreateOffscreenMSAA(
*content_context->GetContext(),
- *GetContentContext()->GetRenderTargetCache(), {100, 100});
+ *GetContentContext()->GetRenderTargetCache(), {100, 100},
+ /*mip_count=*/1);
auto render_pass = buffer->CreateRenderPass(render_target);
Entity entity;
diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc
index 67e343d..576c3a9 100644
--- a/impeller/entity/entity_pass.cc
+++ b/impeller/entity/entity_pass.cc
@@ -247,6 +247,7 @@
static EntityPassTarget CreateRenderTarget(ContentContext& renderer,
ISize size,
+ int mip_count,
const Color& clear_color) {
auto context = renderer.GetContext();
@@ -258,24 +259,27 @@
RenderTarget target;
if (context->GetCapabilities()->SupportsOffscreenMSAA()) {
target = RenderTarget::CreateOffscreenMSAA(
- *context, // context
- *renderer.GetRenderTargetCache(), // allocator
- size, // size
- "EntityPass", // label
+ /*context=*/*context,
+ /*allocator=*/*renderer.GetRenderTargetCache(),
+ /*size=*/size,
+ /*mip_count=*/mip_count,
+ /*label=*/"EntityPass",
+ /*color_attachment_config=*/
RenderTarget::AttachmentConfigMSAA{
.storage_mode = StorageMode::kDeviceTransient,
.resolve_storage_mode = StorageMode::kDevicePrivate,
.load_action = LoadAction::kDontCare,
.store_action = StoreAction::kMultisampleResolve,
- .clear_color = clear_color}, // color_attachment_config
- kDefaultStencilConfig // stencil_attachment_config
- );
+ .clear_color = clear_color},
+ /*stencil_attachment_config=*/
+ kDefaultStencilConfig);
} else {
target = RenderTarget::CreateOffscreen(
*context, // context
*renderer.GetRenderTargetCache(), // allocator
size, // size
- "EntityPass", // label
+ /*mip_count=*/mip_count,
+ "EntityPass", // label
RenderTarget::AttachmentConfig{
.storage_mode = StorageMode::kDevicePrivate,
.load_action = LoadAction::kDontCare,
@@ -321,13 +325,23 @@
Rect::MakeSize(root_render_target.GetRenderTargetSize()),
{.readonly = true});
- IterateAllEntities([lazy_glyph_atlas =
- renderer.GetLazyGlyphAtlas()](const Entity& entity) {
- if (const auto& contents = entity.GetContents()) {
- contents->PopulateGlyphAtlas(lazy_glyph_atlas, entity.DeriveTextScale());
- }
- return true;
- });
+ int32_t required_mip_count = 1;
+ IterateAllElements(
+ [&required_mip_count, lazy_glyph_atlas = renderer.GetLazyGlyphAtlas()](
+ const Element& element) {
+ if (auto entity = std::get_if<Entity>(&element)) {
+ if (const auto& contents = entity->GetContents()) {
+ contents->PopulateGlyphAtlas(lazy_glyph_atlas,
+ entity->DeriveTextScale());
+ }
+ }
+ if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
+ const EntityPass* entity_pass = subpass->get();
+ required_mip_count =
+ std::max(required_mip_count, entity_pass->GetRequiredMipCount());
+ }
+ return true;
+ });
ClipCoverageStack clip_coverage_stack = {ClipCoverageLayer{
.coverage = Rect::MakeSize(root_render_target.GetRenderTargetSize()),
@@ -338,8 +352,8 @@
// and then blit the results onto the onscreen texture. If using this branch,
// there's no need to set up a stencil attachment on the root render target.
if (reads_from_onscreen_backdrop) {
- auto offscreen_target = CreateRenderTarget(
- renderer, root_render_target.GetRenderTargetSize(),
+ EntityPassTarget offscreen_target = CreateRenderTarget(
+ renderer, root_render_target.GetRenderTargetSize(), required_mip_count,
GetClearColorOrDefault(render_target.GetRenderTargetSize()));
if (!OnRender(renderer, // renderer
@@ -599,8 +613,9 @@
}
auto subpass_target = CreateRenderTarget(
- renderer, // renderer
- subpass_size, // size
+ renderer, // renderer
+ subpass_size, // size
+ /*mip_count=*/1,
subpass->GetClearColorOrDefault(subpass_size)); // clear_color
if (!subpass_target.IsValid()) {
@@ -1015,6 +1030,25 @@
}
}
+void EntityPass::IterateAllElements(
+ const std::function<bool(const Element&)>& iterator) const {
+ /// TODO(gaaclarke): Remove duplication here between const and non-const
+ /// versions.
+ if (!iterator) {
+ return;
+ }
+
+ for (auto& element : elements_) {
+ if (!iterator(element)) {
+ return;
+ }
+ if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
+ const EntityPass* entity_pass = subpass->get();
+ entity_pass->IterateAllElements(iterator);
+ }
+ }
+}
+
void EntityPass::IterateAllEntities(
const std::function<bool(Entity&)>& iterator) {
if (!iterator) {
diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h
index 39f5449..7de68d0 100644
--- a/impeller/entity/entity_pass.h
+++ b/impeller/entity/entity_pass.h
@@ -101,6 +101,9 @@
/// it's included in the stream before its children.
void IterateAllElements(const std::function<bool(Element&)>& iterator);
+ void IterateAllElements(
+ const std::function<bool(const Element&)>& iterator) const;
+
//----------------------------------------------------------------------------
/// @brief Iterate all entities in this pass, recursively including entities
/// of child passes. The iteration order is depth-first.
@@ -148,6 +151,12 @@
void SetEnableOffscreenCheckerboard(bool enabled);
+ int32_t GetRequiredMipCount() const { return required_mip_count_; }
+
+ void SetRequiredMipCount(int32_t mip_count) {
+ required_mip_count_ = mip_count;
+ }
+
//----------------------------------------------------------------------------
/// @brief Computes the coverage of a given subpass. This is used to
/// determine the texture size of a given subpass before it's rendered
@@ -297,6 +306,7 @@
std::optional<Rect> bounds_limit_;
std::unique_ptr<EntityPassClipRecorder> clip_replay_ =
std::make_unique<EntityPassClipRecorder>();
+ int32_t required_mip_count_ = 1;
/// These values are incremented whenever something is added to the pass that
/// requires reading from the backdrop texture. Currently, this can happen in
diff --git a/impeller/entity/entity_pass_target_unittests.cc b/impeller/entity/entity_pass_target_unittests.cc
index 390f792..108da36 100644
--- a/impeller/entity/entity_pass_target_unittests.cc
+++ b/impeller/entity/entity_pass_target_unittests.cc
@@ -26,7 +26,8 @@
auto buffer = content_context->GetContext()->CreateCommandBuffer();
auto render_target = RenderTarget::CreateOffscreenMSAA(
*content_context->GetContext(),
- *GetContentContext()->GetRenderTargetCache(), {100, 100});
+ *GetContentContext()->GetRenderTargetCache(), {100, 100},
+ /*mip_count=*/1);
auto entity_pass_target = EntityPassTarget(render_target, false, false);
diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc
index 202cd77..37b926a 100644
--- a/impeller/entity/entity_unittests.cc
+++ b/impeller/entity/entity_unittests.cc
@@ -48,6 +48,7 @@
#include "impeller/renderer/command.h"
#include "impeller/renderer/pipeline_descriptor.h"
#include "impeller/renderer/render_pass.h"
+#include "impeller/renderer/testing/mocks.h"
#include "impeller/renderer/vertex_buffer_builder.h"
#include "impeller/typographer/backends/skia/text_frame_skia.h"
#include "impeller/typographer/backends/skia/typographer_context_skia.h"
@@ -2510,8 +2511,9 @@
.store_action = StoreAction::kDontCare,
.clear_color = Color::BlackTransparent()};
auto rt = RenderTarget::CreateOffscreen(
- *GetContext(), *test_allocator, ISize::MakeWH(1000, 1000), "Offscreen",
- RenderTarget::kDefaultColorAttachmentConfig, stencil_config);
+ *GetContext(), *test_allocator, ISize::MakeWH(1000, 1000),
+ /*mip_count=*/1, "Offscreen", RenderTarget::kDefaultColorAttachmentConfig,
+ stencil_config);
auto content_context = ContentContext(
GetContext(), TypographerContextSkia::Make(), test_allocator);
pass->AddEntity(std::move(entity));
diff --git a/impeller/entity/inline_pass_context.cc b/impeller/entity/inline_pass_context.cc
index b8295c2..1a4589c 100644
--- a/impeller/entity/inline_pass_context.cc
+++ b/impeller/entity/inline_pass_context.cc
@@ -6,11 +6,13 @@
#include <utility>
+#include "flutter/fml/status.h"
#include "impeller/base/allocation.h"
#include "impeller/base/validation.h"
#include "impeller/core/formats.h"
#include "impeller/entity/entity_pass_target.h"
#include "impeller/renderer/command_buffer.h"
+#include "impeller/renderer/texture_mipmap.h"
namespace impeller {
@@ -64,6 +66,15 @@
}
}
+ std::shared_ptr<Texture> target_texture =
+ GetPassTarget().GetRenderTarget().GetRenderTargetTexture();
+ if (target_texture->GetMipCount() > 1) {
+ fml::Status mip_status = AddMipmapGeneration(context_, target_texture);
+ if (!mip_status.ok()) {
+ return false;
+ }
+ }
+
pass_ = nullptr;
command_buffer_ = nullptr;
diff --git a/impeller/entity/render_target_cache.h b/impeller/entity/render_target_cache.h
index b59bd43..382781b 100644
--- a/impeller/entity/render_target_cache.h
+++ b/impeller/entity/render_target_cache.h
@@ -43,6 +43,17 @@
RenderTargetCache(const RenderTargetCache&) = delete;
RenderTargetCache& operator=(const RenderTargetCache&) = delete;
+
+ public:
+ /// Visible for testing.
+ std::vector<TextureData>::const_iterator GetTextureDataBegin() const {
+ return texture_data_.begin();
+ }
+
+ /// Visible for testing.
+ std::vector<TextureData>::const_iterator GetTextureDataEnd() const {
+ return texture_data_.end();
+ }
};
} // namespace impeller
diff --git a/impeller/golden_tests/golden_playground_test_mac.cc b/impeller/golden_tests/golden_playground_test_mac.cc
index 81cec2c..ef7bff9 100644
--- a/impeller/golden_tests/golden_playground_test_mac.cc
+++ b/impeller/golden_tests/golden_playground_test_mac.cc
@@ -55,6 +55,8 @@
"impeller_Play_AiksTest_TextRotated_Vulkan",
// Runtime stage based tests get confused with a Metal context.
"impeller_Play_AiksTest_CanRenderClippedRuntimeEffects_Vulkan",
+ "impeller_Play_AiksTest_CaptureContext_Metal",
+ "impeller_Play_AiksTest_CaptureContext_Vulkan",
};
namespace {
@@ -153,7 +155,20 @@
bool GoldenPlaygroundTest::OpenPlaygroundHere(
AiksPlaygroundCallback
callback) { // NOLINT(performance-unnecessary-value-param)
- return false;
+ AiksContext renderer(GetContext(), typographer_context_);
+
+ std::optional<Picture> picture;
+ std::unique_ptr<testing::MetalScreenshot> screenshot;
+ for (int i = 0; i < 2; ++i) {
+ picture = callback(renderer);
+ if (!picture.has_value()) {
+ return false;
+ }
+ screenshot = pimpl_->screenshotter->MakeScreenshot(
+ renderer, picture.value(), pimpl_->window_size);
+ }
+
+ return SaveScreenshot(std::move(screenshot));
}
std::shared_ptr<Texture> GoldenPlaygroundTest::CreateTextureForFixture(
diff --git a/impeller/renderer/BUILD.gn b/impeller/renderer/BUILD.gn
index 6b17817..3e0c7b5 100644
--- a/impeller/renderer/BUILD.gn
+++ b/impeller/renderer/BUILD.gn
@@ -92,6 +92,8 @@
"snapshot.h",
"surface.cc",
"surface.h",
+ "texture_mipmap.cc",
+ "texture_mipmap.h",
"vertex_buffer_builder.cc",
"vertex_buffer_builder.h",
"vertex_descriptor.cc",
diff --git a/impeller/renderer/render_target.cc b/impeller/renderer/render_target.cc
index 3a9f1e0..d4a1e3c 100644
--- a/impeller/renderer/render_target.cc
+++ b/impeller/renderer/render_target.cc
@@ -224,6 +224,7 @@
const Context& context,
RenderTargetAllocator& allocator,
ISize size,
+ int mip_count,
const std::string& label,
AttachmentConfig color_attachment_config,
std::optional<AttachmentConfig> stencil_attachment_config) {
@@ -237,6 +238,7 @@
color_tex0.storage_mode = color_attachment_config.storage_mode;
color_tex0.format = pixel_format;
color_tex0.size = size;
+ color_tex0.mip_count = mip_count;
color_tex0.usage = static_cast<uint64_t>(TextureUsage::kRenderTarget) |
static_cast<uint64_t>(TextureUsage::kShaderRead);
@@ -266,6 +268,7 @@
const Context& context,
RenderTargetAllocator& allocator,
ISize size,
+ int mip_count,
const std::string& label,
AttachmentConfigMSAA color_attachment_config,
std::optional<AttachmentConfig> stencil_attachment_config) {
@@ -310,6 +313,7 @@
color0_resolve_tex_desc.usage =
static_cast<uint64_t>(TextureUsage::kRenderTarget) |
static_cast<uint64_t>(TextureUsage::kShaderRead);
+ color0_resolve_tex_desc.mip_count = mip_count;
auto color0_resolve_tex = allocator.CreateTexture(color0_resolve_tex_desc);
if (!color0_resolve_tex) {
diff --git a/impeller/renderer/render_target.h b/impeller/renderer/render_target.h
index dbd115a..997ebac 100644
--- a/impeller/renderer/render_target.h
+++ b/impeller/renderer/render_target.h
@@ -86,6 +86,7 @@
const Context& context,
RenderTargetAllocator& allocator,
ISize size,
+ int mip_count,
const std::string& label = "Offscreen",
AttachmentConfig color_attachment_config = kDefaultColorAttachmentConfig,
std::optional<AttachmentConfig> stencil_attachment_config =
@@ -95,6 +96,7 @@
const Context& context,
RenderTargetAllocator& allocator,
ISize size,
+ int mip_count,
const std::string& label = "Offscreen MSAA",
AttachmentConfigMSAA color_attachment_config =
kDefaultColorAttachmentConfigMSAA,
diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc
index e36f634..0881fd6 100644
--- a/impeller/renderer/renderer_unittests.cc
+++ b/impeller/renderer/renderer_unittests.cc
@@ -1261,8 +1261,8 @@
auto render_target_cache = std::make_shared<RenderTargetAllocator>(
GetContext()->GetResourceAllocator());
- auto render_target =
- RenderTarget::CreateOffscreen(*context, *render_target_cache, {100, 100});
+ auto render_target = RenderTarget::CreateOffscreen(
+ *context, *render_target_cache, {100, 100}, /*mip_count=*/1);
auto render_pass = cmd_buffer->CreateRenderPass(render_target);
render_pass->ReserveCommands(100u);
@@ -1276,8 +1276,8 @@
auto render_target_cache = std::make_shared<RenderTargetAllocator>(
GetContext()->GetResourceAllocator());
- auto render_target =
- RenderTarget::CreateOffscreen(*context, *render_target_cache, {100, 100});
+ auto render_target = RenderTarget::CreateOffscreen(
+ *context, *render_target_cache, {100, 100}, /*mip_count=*/1);
auto render_pass = cmd_buffer->CreateRenderPass(render_target);
EXPECT_EQ(render_pass->GetSampleCount(), render_target.GetSampleCount());
diff --git a/impeller/renderer/texture_mipmap.cc b/impeller/renderer/texture_mipmap.cc
new file mode 100644
index 0000000..0a633c6
--- /dev/null
+++ b/impeller/renderer/texture_mipmap.cc
@@ -0,0 +1,31 @@
+// 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/renderer/texture_mipmap.h"
+#include "impeller/renderer/blit_pass.h"
+#include "impeller/renderer/command_buffer.h"
+
+namespace impeller {
+
+fml::Status AddMipmapGeneration(const std::shared_ptr<Context>& context,
+ const std::shared_ptr<Texture>& texture) {
+ std::shared_ptr<CommandBuffer> mip_cmd_buffer =
+ context->CreateCommandBuffer();
+ std::shared_ptr<BlitPass> blit_pass = mip_cmd_buffer->CreateBlitPass();
+ bool success = blit_pass->GenerateMipmap(texture);
+ if (!success) {
+ return fml::Status(fml::StatusCode::kUnknown, "");
+ }
+ success = blit_pass->EncodeCommands(context->GetResourceAllocator());
+ if (!success) {
+ return fml::Status(fml::StatusCode::kUnknown, "");
+ }
+ success = mip_cmd_buffer->SubmitCommands(/*callback=*/nullptr);
+ if (!success) {
+ return fml::Status(fml::StatusCode::kUnknown, "");
+ }
+ return {};
+}
+
+} // namespace impeller
diff --git a/impeller/renderer/texture_mipmap.h b/impeller/renderer/texture_mipmap.h
new file mode 100644
index 0000000..f2f7d8c
--- /dev/null
+++ b/impeller/renderer/texture_mipmap.h
@@ -0,0 +1,21 @@
+// 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_TEXTURE_MIPMAP_H_
+#define FLUTTER_IMPELLER_TEXTURE_MIPMAP_H_
+
+#include "flutter/fml/status.h"
+#include "impeller/core/texture.h"
+#include "impeller/renderer/context.h"
+
+namespace impeller {
+
+/// Adds a blit command to the render pass.
+[[nodiscard]] fml::Status AddMipmapGeneration(
+ const std::shared_ptr<Context>& context,
+ const std::shared_ptr<Texture>& texture);
+
+} // namespace impeller
+
+#endif // FLUTTER_IMPELLER_TEXTURE_MIPMAP_H_