diff --git a/flow/compositor_context.cc b/flow/compositor_context.cc index ddf9f1c698893ecd675c398eaa852e4be80d8775..01e23e057196cf943892d47cbc45cd2171b6b9c5 100644 --- a/flow/compositor_context.cc +++ b/flow/compositor_context.cc @@ -9,8 +9,10 @@ namespace flutter { -CompositorContext::CompositorContext(fml::Milliseconds frame_budget) - : raster_time_(frame_budget), ui_time_(frame_budget) {} +CompositorContext::CompositorContext(Delegate& delegate) + : delegate_(delegate), + raster_time_(delegate.GetFrameBudget()), + ui_time_(delegate.GetFrameBudget()) {} CompositorContext::~CompositorContext() = default; @@ -23,8 +25,11 @@ void CompositorContext::BeginFrame(ScopedFrame& frame, } void CompositorContext::EndFrame(ScopedFrame& frame, - bool enable_instrumentation) { - raster_cache_.SweepAfterFrame(); + bool enable_instrumentation, + size_t freed_hint) { + freed_hint += raster_cache_.SweepAfterFrame(); + delegate_.OnCompositorEndFrame(freed_hint); + if (enable_instrumentation) { raster_time_.Stop(); } @@ -64,7 +69,7 @@ CompositorContext::ScopedFrame::ScopedFrame( } CompositorContext::ScopedFrame::~ScopedFrame() { - context_.EndFrame(*this, instrumentation_enabled_); + context_.EndFrame(*this, instrumentation_enabled_, uncached_external_size_); } RasterStatus CompositorContext::ScopedFrame::Raster( diff --git a/flow/compositor_context.h b/flow/compositor_context.h index 47992abda602865f220eaa2de6b6295067c922e8..b17dc907420daf3bd9bd0e2d65fd6f3b19a91abc 100644 --- a/flow/compositor_context.h +++ b/flow/compositor_context.h @@ -37,6 +37,18 @@ enum class RasterStatus { class CompositorContext { public: + class Delegate { + public: + /// Called at the end of a frame with approximately how many bytes mightbe + /// freed if a GC ran now. + /// + /// This method is called from the raster task runner. + virtual void OnCompositorEndFrame(size_t freed_hint) = 0; + + /// Time limit for a smooth frame. See `Engine::GetDisplayRefreshRate`. + virtual fml::Milliseconds GetFrameBudget() = 0; + }; + class ScopedFrame { public: ScopedFrame(CompositorContext& context, @@ -67,6 +79,8 @@ class CompositorContext { virtual RasterStatus Raster(LayerTree& layer_tree, bool ignore_raster_cache); + void add_external_size(size_t size) { uncached_external_size_ += size; } + private: CompositorContext& context_; GrDirectContext* gr_context_; @@ -76,11 +90,12 @@ class CompositorContext { const bool instrumentation_enabled_; const bool surface_supports_readback_; fml::RefPtr raster_thread_merger_; + size_t uncached_external_size_ = 0; FML_DISALLOW_COPY_AND_ASSIGN(ScopedFrame); }; - CompositorContext(fml::Milliseconds frame_budget = fml::kDefaultFrameBudget); + explicit CompositorContext(Delegate& delegate); virtual ~CompositorContext(); @@ -108,6 +123,7 @@ class CompositorContext { Stopwatch& ui_time() { return ui_time_; } private: + Delegate& delegate_; RasterCache raster_cache_; TextureRegistry texture_registry_; Counter frame_count_; @@ -116,7 +132,9 @@ class CompositorContext { void BeginFrame(ScopedFrame& frame, bool enable_instrumentation); - void EndFrame(ScopedFrame& frame, bool enable_instrumentation); + void EndFrame(ScopedFrame& frame, + bool enable_instrumentation, + size_t freed_hint); FML_DISALLOW_COPY_AND_ASSIGN(CompositorContext); }; diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index a242f977cde2973c78a3d7f25aa03992196ac8f0..490f123ec5f6f95a31575fcaf55cb9af9e519800 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -9,10 +9,11 @@ namespace flutter { -Layer::Layer() +Layer::Layer(size_t external_size) : paint_bounds_(SkRect::MakeEmpty()), unique_id_(NextUniqueID()), - needs_system_composite_(false) {} + needs_system_composite_(false), + external_size_(external_size) {} Layer::~Layer() = default; diff --git a/flow/layers/layer.h b/flow/layers/layer.h index f9732760a7770d43a7d338073427d768f4f0e937..5e75b3937ca186cc8c3e7a3aea77785d8680bf7c 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -67,13 +67,14 @@ struct PrerollContext { // Informs whether a layer needs to be system composited. bool child_scene_layer_exists_below = false; #endif + size_t uncached_external_size = 0; }; // Represents a single composited layer. Created on the UI thread but then // subquently used on the Rasterizer thread. class Layer { public: - Layer(); + Layer(size_t external_size = 0); virtual ~Layer(); virtual void Preroll(PrerollContext* context, const SkMatrix& matrix); @@ -178,6 +179,8 @@ class Layer { uint64_t unique_id() const { return unique_id_; } + size_t external_size() const { return external_size_; } + protected: #if defined(LEGACY_FUCHSIA_EMBEDDER) bool child_layer_exists_below_ = false; @@ -187,6 +190,7 @@ class Layer { SkRect paint_bounds_; uint64_t unique_id_; bool needs_system_composite_; + size_t external_size_ = 0; static uint64_t NextUniqueID(); diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index e0ac5157fc74635231ee6882c6d18311aea1a5d7..160c8c50e1024f72401524e9142c1d052582cce4 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -58,6 +58,7 @@ bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, device_pixel_ratio_}; root_layer_->Preroll(&context, frame.root_surface_transformation()); + frame.add_external_size(context.uncached_external_size); return context.surface_needs_readback; } diff --git a/flow/layers/layer_tree_unittests.cc b/flow/layers/layer_tree_unittests.cc index 7045497692afe2ad89f212e9c7f8856fb5dba3fc..99231f8254eddc2fb4fb23ac3f08cb37d015fe0e 100644 --- a/flow/layers/layer_tree_unittests.cc +++ b/flow/layers/layer_tree_unittests.cc @@ -15,11 +15,11 @@ namespace flutter { namespace testing { -class LayerTreeTest : public CanvasTest { +class LayerTreeTest : public CanvasTest, public CompositorContext::Delegate { public: LayerTreeTest() : layer_tree_(SkISize::Make(64, 64), 1.0f), - compositor_context_(fml::kDefaultFrameBudget), + compositor_context_(*this), root_transform_(SkMatrix::Translate(1.0f, 1.0f)), scoped_frame_(compositor_context_.AcquireFrame(nullptr, &mock_canvas(), @@ -33,11 +33,24 @@ class LayerTreeTest : public CanvasTest { CompositorContext::ScopedFrame& frame() { return *scoped_frame_.get(); } const SkMatrix& root_transform() { return root_transform_; } + // |CompositorContext::Delegate| + void OnCompositorEndFrame(size_t freed_hint) override { + last_freed_hint_ = freed_hint; + } + + // |CompositorContext::Delegate| + fml::Milliseconds GetFrameBudget() override { + return fml::kDefaultFrameBudget; + } + + size_t last_freed_hint() { return last_freed_hint_; } + private: LayerTree layer_tree_; CompositorContext compositor_context_; SkMatrix root_transform_; std::unique_ptr scoped_frame_; + size_t last_freed_hint_ = 0; }; TEST_F(LayerTreeTest, PaintingEmptyLayerDies) { diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index d5d6a34b573e818cbc322672477d426975402398..067a59d782398777490d84aac2042244e192a76b 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -11,8 +11,10 @@ namespace flutter { PictureLayer::PictureLayer(const SkPoint& offset, SkiaGPUObject picture, bool is_complex, - bool will_change) - : offset_(offset), + bool will_change, + size_t external_size) + : Layer(external_size), + offset_(offset), picture_(std::move(picture)), is_complex_(is_complex), will_change_(will_change) {} @@ -26,6 +28,7 @@ void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkPicture* sk_picture = picture(); + bool cached = false; if (auto* cache = context->raster_cache) { TRACE_EVENT0("flutter", "PictureLayer::RasterCache (Preroll)"); @@ -34,8 +37,13 @@ void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #ifndef SUPPORT_FRACTIONAL_TRANSLATION ctm = RasterCache::GetIntegralTransCTM(ctm); #endif - cache->Prepare(context->gr_context, sk_picture, ctm, - context->dst_color_space, is_complex_, will_change_); + cached = cache->Prepare(context->gr_context, sk_picture, ctm, + context->dst_color_space, is_complex_, will_change_, + external_size()); + } + + if (!cached) { + context->uncached_external_size += external_size(); } SkRect bounds = sk_picture->cullRect().makeOffset(offset_.x(), offset_.y()); diff --git a/flow/layers/picture_layer.h b/flow/layers/picture_layer.h index e733e7455ca6c0063fe5d0d404c4cf5c8426ecf0..c86361a9aaaae321442c7d0f70bfdd5c0df98cee 100644 --- a/flow/layers/picture_layer.h +++ b/flow/layers/picture_layer.h @@ -18,7 +18,8 @@ class PictureLayer : public Layer { PictureLayer(const SkPoint& offset, SkiaGPUObject picture, bool is_complex, - bool will_change); + bool will_change, + size_t external_size); SkPicture* picture() const { return picture_.get().get(); } diff --git a/flow/layers/picture_layer_unittests.cc b/flow/layers/picture_layer_unittests.cc index dc9e6080c150889ba5501faabafe1e276b40f002..b7bfce854ed825b13f913e871fe3f57044b58e68 100644 --- a/flow/layers/picture_layer_unittests.cc +++ b/flow/layers/picture_layer_unittests.cc @@ -24,7 +24,7 @@ using PictureLayerTest = SkiaGPUObjectLayerTest; TEST_F(PictureLayerTest, PaintBeforePrerollInvalidPictureDies) { const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); auto layer = std::make_shared( - layer_offset, SkiaGPUObject(), false, false); + layer_offset, SkiaGPUObject(), false, false, 0); EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), "picture_\\.get\\(\\)"); @@ -35,7 +35,8 @@ TEST_F(PictureLayerTest, PaintBeforePreollDies) { const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); auto layer = std::make_shared( - layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false, + 0); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), @@ -47,7 +48,8 @@ TEST_F(PictureLayerTest, PaintingEmptyLayerDies) { const SkRect picture_bounds = SkRect::MakeEmpty(); auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); auto layer = std::make_shared( - layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false, + 0); layer->Preroll(preroll_context(), SkMatrix()); EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); @@ -62,7 +64,7 @@ TEST_F(PictureLayerTest, PaintingEmptyLayerDies) { TEST_F(PictureLayerTest, InvalidPictureDies) { const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); auto layer = std::make_shared( - layer_offset, SkiaGPUObject(), false, false); + layer_offset, SkiaGPUObject(), false, false, 0); // Crashes reading a nullptr. EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), ""); @@ -75,7 +77,10 @@ TEST_F(PictureLayerTest, SimplePicture) { const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); auto layer = std::make_shared( - layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false, + 1000); + + EXPECT_EQ(layer->external_size(), 1000ul); layer->Preroll(preroll_context(), SkMatrix()); EXPECT_EQ(layer->paint_bounds(), diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index 72f55bc7e3bef87c87a67fab5534019deeeb4109..f077d215a28d4ebd9078d4f274c6c2bc83250f3e 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -141,6 +141,7 @@ void RasterCache::Prepare(PrerollContext* context, Entry& entry = layer_cache_[cache_key]; entry.access_count++; entry.used_this_frame = true; + entry.external_size = layer->external_size(); if (!entry.image) { entry.image = RasterizeLayer(context, layer, ctm, checkerboard_images_); } @@ -181,7 +182,8 @@ bool RasterCache::Prepare(GrDirectContext* context, const SkMatrix& transformation_matrix, SkColorSpace* dst_color_space, bool is_complex, - bool will_change) { + bool will_change, + size_t external_size) { // Disabling caching when access_threshold is zero is historic behavior. if (access_threshold_ == 0) { return false; @@ -207,6 +209,7 @@ bool RasterCache::Prepare(GrDirectContext* context, // Creates an entry, if not present prior. Entry& entry = picture_cache_[cache_key]; + entry.external_size = external_size; if (entry.access_count < access_threshold_) { // Frame threshold has not yet been reached. return false; @@ -260,11 +263,12 @@ bool RasterCache::Draw(const Layer* layer, return false; } -void RasterCache::SweepAfterFrame() { - SweepOneCacheAfterFrame(picture_cache_); - SweepOneCacheAfterFrame(layer_cache_); +size_t RasterCache::SweepAfterFrame() { + size_t removed_size = SweepOneCacheAfterFrame(picture_cache_); + removed_size += SweepOneCacheAfterFrame(layer_cache_); picture_cached_this_frame_ = 0; TraceStatsToTimeline(); + return removed_size; } void RasterCache::Clear() { diff --git a/flow/raster_cache.h b/flow/raster_cache.h index 297a58482f05631be0b0de6c64774066df3b0fa6..901757974abc151009a207617b2daf48ae2131fd 100644 --- a/flow/raster_cache.h +++ b/flow/raster_cache.h @@ -137,7 +137,8 @@ class RasterCache { const SkMatrix& transformation_matrix, SkColorSpace* dst_color_space, bool is_complex, - bool will_change); + bool will_change, + size_t external_size = 0); void Prepare(PrerollContext* context, Layer* layer, const SkMatrix& ctm); @@ -156,7 +157,8 @@ class RasterCache { SkCanvas& canvas, SkPaint* paint = nullptr) const; - void SweepAfterFrame(); + /// Returns the amount of external bytes freed by the sweep. + size_t SweepAfterFrame(); void Clear(); @@ -192,17 +194,20 @@ class RasterCache { struct Entry { bool used_this_frame = false; size_t access_count = 0; + size_t external_size = 0; std::unique_ptr image; }; template - static void SweepOneCacheAfterFrame(Cache& cache) { + static size_t SweepOneCacheAfterFrame(Cache& cache) { std::vector dead; + size_t removed_size = 0; for (auto it = cache.begin(); it != cache.end(); ++it) { Entry& entry = it->second; if (!entry.used_this_frame) { dead.push_back(it); + removed_size += entry.external_size; } entry.used_this_frame = false; } @@ -210,6 +215,7 @@ class RasterCache { for (auto it : dead) { cache.erase(it); } + return removed_size; } const size_t access_threshold_; diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index 1c0fa9bd5597f62d9b852d0a98fbe9115ded3637..b60a5f1c67caaad85f1343c14462ca349c85d8a1 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -220,7 +220,7 @@ void SceneBuilder::addPicture(double dx, pictureRect.offset(offset.x(), offset.y()); auto layer = std::make_unique( offset, UIDartState::CreateGPUObject(picture->picture()), !!(hints & 1), - !!(hints & 2)); + !!(hints & 2), picture->GetAllocationSize()); AddLayer(std::move(layer)); } diff --git a/lib/ui/fixtures/ui_test.dart b/lib/ui/fixtures/ui_test.dart index e2fcdaa7c3e8b6be2e82c37fa97cfc279eebdb53..478c9190665621e547f2d131b5fed3ad20b44677 100644 --- a/lib/ui/fixtures/ui_test.dart +++ b/lib/ui/fixtures/ui_test.dart @@ -3,6 +3,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:typed_data'; import 'dart:ui'; @@ -73,3 +74,59 @@ Future encodeImageProducesExternalUint8List() async { void _encodeImage(Image i, int format, void Function(Uint8List result)) native 'EncodeImage'; void _validateExternal(Uint8List result) native 'ValidateExternal'; + +@pragma('vm:entry-point') +Future pumpImage() async { + final FrameCallback renderBlank = (Duration duration) { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawRect(Rect.largest, Paint()); + final Picture picture = recorder.endRecording(); + + final SceneBuilder builder = SceneBuilder(); + builder.addPicture(Offset.zero, picture); + + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + window.onBeginFrame = (Duration duration) { + window.onDrawFrame = _onBeginFrameDone; + }; + window.scheduleFrame(); + }; + + final FrameCallback renderImage = (Duration duration) { + const int width = 8000; + const int height = 8000; + final Completer completer = Completer(); + decodeImageFromPixels( + Uint8List.fromList(List.filled(width * height * 4, 0xFF)), + width, + height, + PixelFormat.rgba8888, + (Image image) => completer.complete(image), + ); + completer.future.then((Image image) { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawImage(image, Offset.zero, Paint()); + final Picture picture = recorder.endRecording(); + + final SceneBuilder builder = SceneBuilder(); + builder.addPicture(Offset.zero, picture); + + _captureImageAndPicture(image, picture); + + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + window.onBeginFrame = renderBlank; + window.scheduleFrame(); + }); + }; + + window.onBeginFrame = renderImage; + window.scheduleFrame(); +} +void _captureImageAndPicture(Image image, Picture picture) native 'CaptureImageAndPicture'; +Future _onBeginFrameDone() native 'OnBeginFrameDone'; diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index f66cfed778660cdbab0b6d0e1633feb4287f3188..9c81d6a759cdb96d5a3aea5b217251993c3e67eb 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -233,7 +233,7 @@ bool RuntimeController::ReportTimings(std::vector timings) { return false; } -bool RuntimeController::NotifyIdle(int64_t deadline) { +bool RuntimeController::NotifyIdle(int64_t deadline, size_t freed_hint) { std::shared_ptr root_isolate = root_isolate_.lock(); if (!root_isolate) { return false; @@ -241,6 +241,9 @@ bool RuntimeController::NotifyIdle(int64_t deadline) { tonic::DartState::Scope scope(root_isolate); + // Dart will use the freed hint at the next idle notification. Make sure to + // Update it with our latest value before calling NotifyIdle. + Dart_HintFreed(freed_hint); Dart_NotifyIdle(deadline); // Idle notifications being in isolate scope are part of the contract. diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 18adbc2c1d72006149d1311eca2314e21003adb3..e1b29f127a79f42ae2fcfd6c892d2529e55fe073 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -329,9 +329,12 @@ class RuntimeController : public PlatformConfigurationClient { /// system's monotonic time. The clock can be accessed via /// `Dart_TimelineGetMicros`. /// + /// @param[in] freed_hint A hint of the number of bytes potentially freed + /// since the last call to NotifyIdle if a GC were run. + /// /// @return If the idle notification was forwarded to the running isolate. /// - bool NotifyIdle(int64_t deadline); + bool NotifyIdle(int64_t deadline, size_t freed_hint); //---------------------------------------------------------------------------- /// @brief Returns if the root isolate is running. The isolate must be diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 315780f23346bb81799d3f714b78d456568c902d..dbbac6e583b15015630eecbc0e3564ecb4073dea 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -248,11 +248,16 @@ void Engine::ReportTimings(std::vector timings) { runtime_controller_->ReportTimings(std::move(timings)); } +void Engine::HintFreed(size_t size) { + hint_freed_bytes_since_last_idle_ += size; +} + void Engine::NotifyIdle(int64_t deadline) { auto trace_event = std::to_string(deadline - Dart_TimelineGetMicros()); TRACE_EVENT1("flutter", "Engine::NotifyIdle", "deadline_now_delta", trace_event.c_str()); - runtime_controller_->NotifyIdle(deadline); + runtime_controller_->NotifyIdle(deadline, hint_freed_bytes_since_last_idle_); + hint_freed_bytes_since_last_idle_ = 0; } std::pair Engine::GetUIIsolateReturnCode() { diff --git a/shell/common/engine.h b/shell/common/engine.h index d16ae0ffed2c7952fdfae5265af7e849145a4174..97165f42a69fa72f62faeeb7ba12cb38ec0eed10 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -465,6 +465,14 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// void BeginFrame(fml::TimePoint frame_time); + //---------------------------------------------------------------------------- + /// @brief Notifies the engine that native bytes might be freed if a + /// garbage collection ran now. + /// + /// @param[in] size The number of bytes freed. + /// + void HintFreed(size_t size); + //---------------------------------------------------------------------------- /// @brief Notifies the engine that the UI task runner is not expected to /// undertake a new frame workload till a specified timepoint. The @@ -797,6 +805,7 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { FontCollection font_collection_; ImageDecoder image_decoder_; TaskRunners task_runners_; + size_t hint_freed_bytes_since_last_idle_ = 0; fml::WeakPtrFactory weak_factory_; // |RuntimeDelegate| diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 292de2e234f182e39a6a33c3d7bbaed337794e9e..682d3a6d2672f61aed438e6357e4494b05477a10 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -27,8 +27,7 @@ static constexpr std::chrono::milliseconds kSkiaCleanupExpiration(15000); Rasterizer::Rasterizer(Delegate& delegate) : Rasterizer(delegate, - std::make_unique( - delegate.GetFrameBudget())) {} + std::make_unique(delegate)) {} Rasterizer::Rasterizer( Delegate& delegate, diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index 8fbfd973150ea6195bf42f8ef64d63a033555160..963a0998db4657892c9a261f2695601d0ce3264f 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -50,7 +50,7 @@ class Rasterizer final : public SnapshotDelegate { /// are made on the GPU task runner. Any delegate must ensure that /// they can handle the threading implications. /// - class Delegate { + class Delegate : public CompositorContext::Delegate { public: //-------------------------------------------------------------------------- /// @brief Notifies the delegate that a frame has been rendered. The diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 97c4cde047d27105932c2237dd6272350f60f47d..f4651184c0e08e4790b5dfccd8121a64e4b770d5 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1179,6 +1179,16 @@ void Shell::OnFrameRasterized(const FrameTiming& timing) { } } +void Shell::OnCompositorEndFrame(size_t freed_hint) { + FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); + task_runners_.GetUITaskRunner()->PostTask( + [engine = weak_engine_, freed_hint = freed_hint]() { + if (engine) { + engine->HintFreed(freed_hint); + } + }); +} + fml::Milliseconds Shell::GetFrameBudget() { if (display_refresh_rate_ > 0) { return fml::RefreshRateToFrameBudget(display_refresh_rate_.load()); diff --git a/shell/common/shell.h b/shell/common/shell.h index dd09fad2ae7a61d5caed07c2b2fd7df7585548ea..aa9b64ae1ee32a5e622de814bd1e537d5f61afe4 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -12,6 +12,7 @@ #include "flutter/common/settings.h" #include "flutter/common/task_runners.h" +#include "flutter/flow/compositor_context.h" #include "flutter/flow/surface.h" #include "flutter/flow/texture.h" #include "flutter/fml/closure.h" @@ -528,10 +529,14 @@ class Shell final : public PlatformView::Delegate, void OnFrameRasterized(const FrameTiming&) override; // |Rasterizer::Delegate| - fml::Milliseconds GetFrameBudget() override; + fml::TimePoint GetLatestFrameTargetTime() const override; // |Rasterizer::Delegate| - fml::TimePoint GetLatestFrameTargetTime() const override; + // |CompositorContext::Delegate| + fml::Milliseconds GetFrameBudget() override; + + // |CompositorContext::Delegate| + void OnCompositorEndFrame(size_t freed_hint) override; // |ServiceProtocol::Handler| fml::RefPtr GetServiceProtocolHandlerTaskRunner( diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 8144d3accaacd02908af914e0f3eb09f263c8446..e80652e33c059b9cad3b6bcf987777ca96003568 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -106,6 +106,40 @@ void ShellTest::VSyncFlush(Shell* shell, bool& will_draw_new_frame) { latch.Wait(); } +void ShellTest::SetViewportMetrics(Shell* shell, double width, double height) { + flutter::ViewportMetrics viewport_metrics = { + 1, // device pixel ratio + width, // physical width + height, // physical height + 0, // padding top + 0, // padding right + 0, // padding bottom + 0, // padding left + 0, // view inset top + 0, // view inset right + 0, // view inset bottom + 0, // view inset left + 0, // gesture inset top + 0, // gesture inset right + 0, // gesture inset bottom + 0 // gesture inset left + }; + // Set viewport to nonempty, and call Animator::BeginFrame to make the layer + // tree pipeline nonempty. Without either of this, the layer tree below + // won't be rasterized. + fml::AutoResetWaitableEvent latch; + shell->GetTaskRunners().GetUITaskRunner()->PostTask( + [&latch, engine = shell->weak_engine_, viewport_metrics]() { + engine->SetViewportMetrics(std::move(viewport_metrics)); + const auto frame_begin_time = fml::TimePoint::Now(); + const auto frame_end_time = + frame_begin_time + fml::TimeDelta::FromSecondsF(1.0 / 60.0); + engine->animator_->BeginFrame(frame_begin_time, frame_end_time); + latch.Signal(); + }); + latch.Wait(); +} + void ShellTest::PumpOneFrame(Shell* shell, double width, double height, diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index c61aba37c7647ee4a612edfea76d7e443fd2568f..5b626289cd766af170a0889a79b8d063b66d8d1a 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -59,6 +59,7 @@ class ShellTest : public FixtureTest { /// the `will_draw_new_frame` to true. static void VSyncFlush(Shell* shell, bool& will_draw_new_frame); + static void SetViewportMetrics(Shell* shell, double width, double height); /// Given the root layer, this callback builds the layer tree to be rasterized /// in PumpOneFrame. using LayerTreeBuilder = diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 2f4e87fb12ededed34e1a08182f9289d38f91e24..3387a5cea0fdde30b24618d7359a8cad6b616972 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -536,7 +536,8 @@ TEST_F(ShellTest, ExternalEmbedderNoThreadMerger) { this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); auto picture_layer = std::make_shared( SkPoint::Make(10, 10), - flutter::SkiaGPUObject({sk_picture, queue}), false, false); + flutter::SkiaGPUObject({sk_picture, queue}), false, false, + 0); root->Add(picture_layer); }; @@ -585,7 +586,8 @@ TEST_F(ShellTest, this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); auto picture_layer = std::make_shared( SkPoint::Make(10, 10), - flutter::SkiaGPUObject({sk_picture, queue}), false, false); + flutter::SkiaGPUObject({sk_picture, queue}), false, false, + 0); root->Add(picture_layer); }; @@ -1460,7 +1462,8 @@ TEST_F(ShellTest, Screenshot) { this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0)); auto picture_layer = std::make_shared( SkPoint::Make(10, 10), - flutter::SkiaGPUObject({sk_picture, queue}), false, false); + flutter::SkiaGPUObject({sk_picture, queue}), false, false, + 0); root->Add(picture_layer); }; @@ -1746,7 +1749,7 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { auto picture_layer = std::make_shared( SkPoint::Make(0, 0), flutter::SkiaGPUObject({MakeSizedPicture(100, 100), queue}), - false, false); + false, false, 0); picture_layer->set_paint_bounds(SkRect::MakeWH(100, 100)); // 2. Rasterize the picture and the picture layer in the raster cache. diff --git a/shell/common/skp_shader_warmup_unittests.cc b/shell/common/skp_shader_warmup_unittests.cc index 689f2d9322fccbcc8636de868dbaa754c9d4ad6d..ec81003c8f04fa7907b30a57d43bd1c32a3d1d04 100644 --- a/shell/common/skp_shader_warmup_unittests.cc +++ b/shell/common/skp_shader_warmup_unittests.cc @@ -153,7 +153,8 @@ class SkpWarmupTest : public ShellTest { auto picture_layer = std::make_shared( SkPoint::Make(0, 0), SkiaGPUObject(picture, queue), /* is_complex */ false, - /* will_change */ false); + /* will_change */ false, + /* external_size */ 0); root->Add(picture_layer); }; PumpOneFrame(shell.get(), picture->cullRect().width(), @@ -235,7 +236,8 @@ TEST_F(SkpWarmupTest, Image) { auto picture_layer = std::make_shared( SkPoint::Make(0, 0), SkiaGPUObject(picture, queue), /* is_complex */ false, - /* will_change */ false); + /* will_change */ false, + /* external_size */ 0); root->Add(picture_layer); }; diff --git a/shell/platform/fuchsia/flutter/compositor_context.cc b/shell/platform/fuchsia/flutter/compositor_context.cc index 599ebf77b0dd23e977544b20c3955e44cc8f2cdb..6911ed8ddc2d5fa0195fb883d64493aaff6ead11 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.cc +++ b/shell/platform/fuchsia/flutter/compositor_context.cc @@ -140,10 +140,12 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { }; CompositorContext::CompositorContext( + flutter::CompositorContext::Delegate& delegate, SessionConnection& session_connection, VulkanSurfaceProducer& surface_producer, flutter::SceneUpdateContext& scene_update_context) - : session_connection_(session_connection), + : flutter::CompositorContext(delegate), + session_connection_(session_connection), surface_producer_(surface_producer), scene_update_context_(scene_update_context) {} diff --git a/shell/platform/fuchsia/flutter/compositor_context.h b/shell/platform/fuchsia/flutter/compositor_context.h index 542e5d314fa71ef55aa20c39d7d020236a617c92..eb57321c1215c2a211459d936a54f7d951e0f316 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.h +++ b/shell/platform/fuchsia/flutter/compositor_context.h @@ -21,7 +21,8 @@ namespace flutter_runner { // Fuchsia. class CompositorContext final : public flutter::CompositorContext { public: - CompositorContext(SessionConnection& session_connection, + CompositorContext(CompositorContext::Delegate& delegate, + SessionConnection& session_connection, VulkanSurfaceProducer& surface_producer, flutter::SceneUpdateContext& scene_update_context); diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index f7c9a251234f26fdded9e869be7b82e4308ce7ff..f05e4c7904c80463b744eb7060932f166e62e7ef 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -216,7 +216,7 @@ Engine::Engine(Delegate& delegate, std::unique_ptr compositor_context = std::make_unique( - session_connection_.value(), surface_producer_.value(), + shell, session_connection_.value(), surface_producer_.value(), scene_update_context_.value()); return std::make_unique(