diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 8b069e99fc686..8addf725c1d76 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -5553,6 +5553,8 @@ ORIGIN: ../../../flutter/impeller/renderer/snapshot.h + ../../../flutter/LICENSE 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/snapshot.h 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 d001c189e3b0d..3be53f953713b 100644 --- a/impeller/aiks/aiks_context.cc +++ b/impeller/aiks/aiks_context.cc @@ -12,14 +12,18 @@ namespace impeller { AiksContext::AiksContext( std::shared_ptr context, - std::shared_ptr typographer_context) + std::shared_ptr typographer_context, + std::optional> + render_target_allocator) : context_(std::move(context)) { if (!context_ || !context_->IsValid()) { return; } content_context_ = std::make_unique( - 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 ad8d6429c4d01..5b35b63def700 100644 --- a/impeller/aiks/aiks_context.h +++ b/impeller/aiks/aiks_context.h @@ -28,8 +28,12 @@ class AiksContext { /// `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, - std::shared_ptr typographer_context); + std::shared_ptr typographer_context, + std::optional> + render_target_allocator = std::nullopt); ~AiksContext(); diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index e1f75aadc4099..c2b07397610a1 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 #include #include #include @@ -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 @@ TEST_P(AiksTest, SubpassWithClearColorOptimization) { // 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 { + 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>(&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 cache = + std::make_shared(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 c5b9ae9e598b7..f66da9a914cbf 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -133,6 +133,37 @@ void Canvas::Save() { 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& backdrop_filter) { @@ -156,6 +187,9 @@ void Canvas::Save(bool create_subpass, 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 ea07dfd27a58c..8fc31dc31f45b 100644 --- a/impeller/aiks/picture.cc +++ b/impeller/aiks/picture.cc @@ -64,6 +64,7 @@ std::shared_ptr Picture::RenderToTexture( *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 @@ std::shared_ptr Picture::RenderToTexture( ); } 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 b28dbb27ab5f3..af14a9e9331c9 100644 --- a/impeller/core/texture.h +++ b/impeller/core/texture.h @@ -45,6 +45,9 @@ class Texture { 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 46328620026b6..63a2cbdd3aa07 100644 --- a/impeller/entity/contents/checkerboard_contents_unittests.cc +++ b/impeller/entity/contents/checkerboard_contents_unittests.cc @@ -35,7 +35,8 @@ TEST_P(EntityTest, RendersWithoutError) { 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 634ae0b8e6fa7..7680481181b25 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -418,14 +418,14 @@ fml::StatusOr ContentContext::MakeSubpass( 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 5f2c310f78d9e..235a53134c5e3 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -50,6 +50,12 @@ std::shared_ptr FilterContents::MakeDirectionalGaussianBlur( 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::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 3e210d8f0fb70..076593f644dab 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -20,6 +20,8 @@ namespace impeller { 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 c83f0e0d79647..11d192c0357e3 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 { @@ -192,7 +193,6 @@ Rect MakeReferenceUVs(const Rect& reference, const Rect& rect) { rect.GetSize()); return result.Scale(1.0f / Vector2(reference.GetSize())); } - } // namespace GaussianBlurFilterContents::GaussianBlurFilterContents( @@ -284,6 +284,12 @@ std::optional GaussianBlurFilterContents::RenderFilter( 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 54eb9f5cd29a4..f5da62830ecbc 100644 --- a/impeller/entity/contents/scene_contents.cc +++ b/impeller/entity/contents/scene_contents.cc @@ -50,7 +50,8 @@ bool SceneContents::Render(const ContentContext& renderer, *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 @@ bool SceneContents::Render(const ContentContext& renderer, *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 2060f23437c2f..4715eaad79133 100644 --- a/impeller/entity/contents/tiled_texture_contents_unittests.cc +++ b/impeller/entity/contents/tiled_texture_contents_unittests.cc @@ -33,7 +33,8 @@ TEST_P(EntityTest, TiledTextureContentsRendersWithCorrectPipeline) { 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 @@ TEST_P(EntityTest, TiledTextureContentsRendersWithCorrectPipelineExternalOES) { 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 173b13ea4bcfb..17fac72d722dc 100644 --- a/impeller/entity/contents/vertices_contents_unittests.cc +++ b/impeller/entity/contents/vertices_contents_unittests.cc @@ -61,7 +61,8 @@ TEST_P(EntityTest, RendersDstPerColorWithAlpha) { 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 67e343de8a6d5..576c3a97a6ff9 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -247,6 +247,7 @@ static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig = static EntityPassTarget CreateRenderTarget(ContentContext& renderer, ISize size, + int mip_count, const Color& clear_color) { auto context = renderer.GetContext(); @@ -258,24 +259,27 @@ static EntityPassTarget CreateRenderTarget(ContentContext& renderer, 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 @@ bool EntityPass::Render(ContentContext& renderer, 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(&element)) { + if (const auto& contents = entity->GetContents()) { + contents->PopulateGlyphAtlas(lazy_glyph_atlas, + entity->DeriveTextScale()); + } + } + if (auto subpass = std::get_if>(&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 @@ bool EntityPass::Render(ContentContext& renderer, // 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 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( } 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( } } +void EntityPass::IterateAllElements( + const std::function& 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>(&element)) { + const EntityPass* entity_pass = subpass->get(); + entity_pass->IterateAllElements(iterator); + } + } +} + void EntityPass::IterateAllEntities( const std::function& iterator) { if (!iterator) { diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 39f5449ff323b..7de68d0d9ad11 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -101,6 +101,9 @@ class EntityPass { /// it's included in the stream before its children. void IterateAllElements(const std::function& iterator); + void IterateAllElements( + const std::function& 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 @@ class EntityPass { 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 @@ class EntityPass { std::optional bounds_limit_; std::unique_ptr clip_replay_ = std::make_unique(); + 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 390f7929913f7..108da36230506 100644 --- a/impeller/entity/entity_pass_target_unittests.cc +++ b/impeller/entity/entity_pass_target_unittests.cc @@ -26,7 +26,8 @@ TEST_P(EntityPassTargetTest, SwapWithMSAATexture) { 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 520d2ee9ee3db..4bde8ea6799f7 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" @@ -2511,8 +2512,9 @@ TEST_P(EntityTest, AdvancedBlendCoverageHintIsNotResetByEntityPass) { .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 b8295c2cb444a..1a4589c24d32f 100644 --- a/impeller/entity/inline_pass_context.cc +++ b/impeller/entity/inline_pass_context.cc @@ -6,11 +6,13 @@ #include +#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 @@ bool InlinePassContext::EndPass() { } } + std::shared_ptr 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 b59bd43983711..382781b793b7b 100644 --- a/impeller/entity/render_target_cache.h +++ b/impeller/entity/render_target_cache.h @@ -43,6 +43,17 @@ class RenderTargetCache : public RenderTargetAllocator { RenderTargetCache(const RenderTargetCache&) = delete; RenderTargetCache& operator=(const RenderTargetCache&) = delete; + + public: + /// Visible for testing. + std::vector::const_iterator GetTextureDataBegin() const { + return texture_data_.begin(); + } + + /// Visible for testing. + std::vector::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 81cec2c283a10..ef7bff92ac2c1 100644 --- a/impeller/golden_tests/golden_playground_test_mac.cc +++ b/impeller/golden_tests/golden_playground_test_mac.cc @@ -55,6 +55,8 @@ static const std::vector kSkipTests = { "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(Picture picture) { bool GoldenPlaygroundTest::OpenPlaygroundHere( AiksPlaygroundCallback callback) { // NOLINT(performance-unnecessary-value-param) - return false; + AiksContext renderer(GetContext(), typographer_context_); + + std::optional picture; + std::unique_ptr 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 GoldenPlaygroundTest::CreateTextureForFixture( diff --git a/impeller/renderer/BUILD.gn b/impeller/renderer/BUILD.gn index 6b17817b849f3..3e0c7b5770420 100644 --- a/impeller/renderer/BUILD.gn +++ b/impeller/renderer/BUILD.gn @@ -92,6 +92,8 @@ impeller_component("renderer") { "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 3a9f1e0468e81..d4a1e3c6cf0bb 100644 --- a/impeller/renderer/render_target.cc +++ b/impeller/renderer/render_target.cc @@ -224,6 +224,7 @@ RenderTarget RenderTarget::CreateOffscreen( const Context& context, RenderTargetAllocator& allocator, ISize size, + int mip_count, const std::string& label, AttachmentConfig color_attachment_config, std::optional stencil_attachment_config) { @@ -237,6 +238,7 @@ RenderTarget RenderTarget::CreateOffscreen( 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(TextureUsage::kRenderTarget) | static_cast(TextureUsage::kShaderRead); @@ -266,6 +268,7 @@ RenderTarget RenderTarget::CreateOffscreenMSAA( const Context& context, RenderTargetAllocator& allocator, ISize size, + int mip_count, const std::string& label, AttachmentConfigMSAA color_attachment_config, std::optional stencil_attachment_config) { @@ -310,6 +313,7 @@ RenderTarget RenderTarget::CreateOffscreenMSAA( color0_resolve_tex_desc.usage = static_cast(TextureUsage::kRenderTarget) | static_cast(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 dbd115a5c7612..997ebac1b7eab 100644 --- a/impeller/renderer/render_target.h +++ b/impeller/renderer/render_target.h @@ -86,6 +86,7 @@ class RenderTarget final { const Context& context, RenderTargetAllocator& allocator, ISize size, + int mip_count, const std::string& label = "Offscreen", AttachmentConfig color_attachment_config = kDefaultColorAttachmentConfig, std::optional stencil_attachment_config = @@ -95,6 +96,7 @@ class RenderTarget final { 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 dcc40359f9d27..f34b2440b6b6b 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -1286,8 +1286,8 @@ TEST_P(RendererTest, CanPreAllocateCommands) { auto render_target_cache = std::make_shared( 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); @@ -1301,8 +1301,8 @@ TEST_P(RendererTest, CanLookupRenderTargetProperties) { auto render_target_cache = std::make_shared( 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 0000000000000..0a633c645958a --- /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, + const std::shared_ptr& texture) { + std::shared_ptr mip_cmd_buffer = + context->CreateCommandBuffer(); + std::shared_ptr 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 0000000000000..f2f7d8cbb46d5 --- /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, + const std::shared_ptr& texture); + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_TEXTURE_MIPMAP_H_