1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4// FLUTTER_NOLINT
5
6#include "flutter/shell/common/rasterizer.h"
7
8#include <utility>
9
10#include "flutter/fml/time/time_delta.h"
11#include "flutter/fml/time/time_point.h"
12#include "flutter/shell/common/persistent_cache.h"
13#include "flutter/shell/common/serialization_callbacks.h"
14#include "third_party/skia/include/core/SkEncodedImageFormat.h"
15#include "third_party/skia/include/core/SkImageEncoder.h"
16#include "third_party/skia/include/core/SkPictureRecorder.h"
17#include "third_party/skia/include/core/SkSerialProcs.h"
18#include "third_party/skia/include/core/SkSurface.h"
19#include "third_party/skia/include/core/SkSurfaceCharacterization.h"
20#include "third_party/skia/include/utils/SkBase64.h"
21
22namespace flutter {
23
24// The rasterizer will tell Skia to purge cached resources that have not been
25// used within this interval.
26static constexpr std::chrono::milliseconds kSkiaCleanupExpiration(15000);
27
28Rasterizer::Rasterizer(Delegate& delegate)
29 : Rasterizer(delegate,
30 std::make_unique<flutter::CompositorContext>(
31 delegate.GetFrameBudget())) {}
32
33Rasterizer::Rasterizer(
34 Delegate& delegate,
35 std::unique_ptr<flutter::CompositorContext> compositor_context)
36 : delegate_(delegate),
37 compositor_context_(std::move(compositor_context)),
38 user_override_resource_cache_bytes_(false),
39 weak_factory_(this) {
40 FML_DCHECK(compositor_context_);
41}
42
43Rasterizer::~Rasterizer() = default;
44
45fml::TaskRunnerAffineWeakPtr<Rasterizer> Rasterizer::GetWeakPtr() const {
46 return weak_factory_.GetWeakPtr();
47}
48
49fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> Rasterizer::GetSnapshotDelegate()
50 const {
51 return weak_factory_.GetWeakPtr();
52}
53
54void Rasterizer::Setup(std::unique_ptr<Surface> surface) {
55 surface_ = std::move(surface);
56 if (max_cache_bytes_.has_value()) {
57 SetResourceCacheMaxBytes(max_cache_bytes_.value(),
58 user_override_resource_cache_bytes_);
59 }
60 compositor_context_->OnGrContextCreated();
61#if !defined(OS_FUCHSIA)
62 // TODO(sanjayc77): https://github.com/flutter/flutter/issues/53179. Add
63 // support for raster thread merger for Fuchsia.
64 if (surface_->GetExternalViewEmbedder()) {
65 const auto platform_id =
66 delegate_.GetTaskRunners().GetPlatformTaskRunner()->GetTaskQueueId();
67 const auto gpu_id =
68 delegate_.GetTaskRunners().GetRasterTaskRunner()->GetTaskQueueId();
69 raster_thread_merger_ =
70 fml::MakeRefCounted<fml::RasterThreadMerger>(platform_id, gpu_id);
71 }
72#endif
73}
74
75void Rasterizer::Teardown() {
76 compositor_context_->OnGrContextDestroyed();
77 surface_.reset();
78 last_layer_tree_.reset();
79}
80
81void Rasterizer::NotifyLowMemoryWarning() const {
82 if (!surface_) {
83 FML_DLOG(INFO)
84 << "Rasterizer::NotifyLowMemoryWarning called with no surface.";
85 return;
86 }
87 auto context = surface_->GetContext();
88 if (!context) {
89 FML_DLOG(INFO)
90 << "Rasterizer::NotifyLowMemoryWarning called with no GrContext.";
91 return;
92 }
93 context->performDeferredCleanup(std::chrono::milliseconds(0));
94}
95
96flutter::TextureRegistry* Rasterizer::GetTextureRegistry() {
97 return &compositor_context_->texture_registry();
98}
99
100flutter::LayerTree* Rasterizer::GetLastLayerTree() {
101 return last_layer_tree_.get();
102}
103
104void Rasterizer::DrawLastLayerTree() {
105 if (!last_layer_tree_ || !surface_) {
106 return;
107 }
108 DrawToSurface(*last_layer_tree_);
109}
110
111void Rasterizer::Draw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline) {
112 TRACE_EVENT0("flutter", "GPURasterizer::Draw");
113 if (raster_thread_merger_ &&
114 !raster_thread_merger_->IsOnRasterizingThread()) {
115 // we yield and let this frame be serviced on the right thread.
116 return;
117 }
118 FML_DCHECK(delegate_.GetTaskRunners()
119 .GetRasterTaskRunner()
120 ->RunsTasksOnCurrentThread());
121
122 RasterStatus raster_status = RasterStatus::kFailed;
123 Pipeline<flutter::LayerTree>::Consumer consumer =
124 [&](std::unique_ptr<LayerTree> layer_tree) {
125 raster_status = DoDraw(std::move(layer_tree));
126 };
127
128 PipelineConsumeResult consume_result = pipeline->Consume(consumer);
129 // if the raster status is to resubmit the frame, we push the frame to the
130 // front of the queue and also change the consume status to more available.
131 if (raster_status == RasterStatus::kResubmit) {
132 auto front_continuation = pipeline->ProduceIfEmpty();
133 bool result =
134 front_continuation.Complete(std::move(resubmitted_layer_tree_));
135 if (result) {
136 consume_result = PipelineConsumeResult::MoreAvailable;
137 }
138 } else if (raster_status == RasterStatus::kEnqueuePipeline) {
139 consume_result = PipelineConsumeResult::MoreAvailable;
140 }
141
142 // Merging the thread as we know the next `Draw` should be run on the platform
143 // thread.
144 if (surface_ != nullptr && surface_->GetExternalViewEmbedder() != nullptr) {
145 auto should_resubmit_frame = raster_status == RasterStatus::kResubmit;
146 surface_->GetExternalViewEmbedder()->EndFrame(should_resubmit_frame,
147 raster_thread_merger_);
148 }
149
150 // Consume as many pipeline items as possible. But yield the event loop
151 // between successive tries.
152 switch (consume_result) {
153 case PipelineConsumeResult::MoreAvailable: {
154 delegate_.GetTaskRunners().GetRasterTaskRunner()->PostTask(
155 [weak_this = weak_factory_.GetWeakPtr(), pipeline]() {
156 if (weak_this) {
157 weak_this->Draw(pipeline);
158 }
159 });
160 break;
161 }
162 default:
163 break;
164 }
165}
166
167namespace {
168sk_sp<SkImage> DrawSnapshot(
169 sk_sp<SkSurface> surface,
170 const std::function<void(SkCanvas*)>& draw_callback) {
171 if (surface == nullptr || surface->getCanvas() == nullptr) {
172 return nullptr;
173 }
174
175 draw_callback(surface->getCanvas());
176 surface->getCanvas()->flush();
177
178 sk_sp<SkImage> device_snapshot;
179 {
180 TRACE_EVENT0("flutter", "MakeDeviceSnpashot");
181 device_snapshot = surface->makeImageSnapshot();
182 }
183
184 if (device_snapshot == nullptr) {
185 return nullptr;
186 }
187
188 {
189 TRACE_EVENT0("flutter", "DeviceHostTransfer");
190 if (auto raster_image = device_snapshot->makeRasterImage()) {
191 return raster_image;
192 }
193 }
194
195 return nullptr;
196}
197} // namespace
198
199sk_sp<SkImage> Rasterizer::DoMakeRasterSnapshot(
200 SkISize size,
201 std::function<void(SkCanvas*)> draw_callback) {
202 TRACE_EVENT0("flutter", __FUNCTION__);
203 sk_sp<SkImage> result;
204 SkImageInfo image_info = SkImageInfo::MakeN32Premul(
205 size.width(), size.height(), SkColorSpace::MakeSRGB());
206 if (surface_ == nullptr || surface_->GetContext() == nullptr) {
207 // Raster surface is fine if there is no on screen surface. This might
208 // happen in case of software rendering.
209 sk_sp<SkSurface> surface = SkSurface::MakeRaster(image_info);
210 result = DrawSnapshot(surface, draw_callback);
211 } else {
212 delegate_.GetIsGpuDisabledSyncSwitch()->Execute(
213 fml::SyncSwitch::Handlers()
214 .SetIfTrue([&] {
215 sk_sp<SkSurface> surface = SkSurface::MakeRaster(image_info);
216 result = DrawSnapshot(surface, draw_callback);
217 })
218 .SetIfFalse([&] {
219 auto context_switch = surface_->MakeRenderContextCurrent();
220 if (!context_switch->GetResult()) {
221 return;
222 }
223
224 // When there is an on screen surface, we need a render target
225 // SkSurface because we want to access texture backed images.
226 sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
227 surface_->GetContext(), // context
228 SkBudgeted::kNo, // budgeted
229 image_info // image info
230 );
231 result = DrawSnapshot(surface, draw_callback);
232 }));
233 }
234
235 return result;
236}
237
238sk_sp<SkImage> Rasterizer::MakeRasterSnapshot(sk_sp<SkPicture> picture,
239 SkISize picture_size) {
240 return DoMakeRasterSnapshot(picture_size,
241 [picture = std::move(picture)](SkCanvas* canvas) {
242 canvas->drawPicture(picture);
243 });
244}
245
246sk_sp<SkImage> Rasterizer::ConvertToRasterImage(sk_sp<SkImage> image) {
247 TRACE_EVENT0("flutter", __FUNCTION__);
248
249 // If the rasterizer does not have a surface with a GrContext, then it will
250 // be unable to render a cross-context SkImage. The caller will need to
251 // create the raster image on the IO thread.
252 if (surface_ == nullptr || surface_->GetContext() == nullptr) {
253 return nullptr;
254 }
255
256 if (image == nullptr) {
257 return nullptr;
258 }
259
260 return DoMakeRasterSnapshot(image->dimensions(),
261 [image = std::move(image)](SkCanvas* canvas) {
262 canvas->drawImage(image, 0, 0);
263 });
264}
265
266RasterStatus Rasterizer::DoDraw(
267 std::unique_ptr<flutter::LayerTree> layer_tree) {
268 FML_DCHECK(delegate_.GetTaskRunners()
269 .GetRasterTaskRunner()
270 ->RunsTasksOnCurrentThread());
271
272 if (!layer_tree || !surface_) {
273 return RasterStatus::kFailed;
274 }
275
276 FrameTiming timing;
277#if !defined(OS_FUCHSIA)
278 const fml::TimePoint frame_target_time = layer_tree->target_time();
279#endif
280 timing.Set(FrameTiming::kVsyncStart, layer_tree->vsync_start());
281 timing.Set(FrameTiming::kBuildStart, layer_tree->build_start());
282 timing.Set(FrameTiming::kBuildFinish, layer_tree->build_finish());
283 timing.Set(FrameTiming::kRasterStart, fml::TimePoint::Now());
284
285 PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess();
286 persistent_cache->ResetStoredNewShaders();
287
288 RasterStatus raster_status = DrawToSurface(*layer_tree);
289 if (raster_status == RasterStatus::kSuccess) {
290 last_layer_tree_ = std::move(layer_tree);
291 } else if (raster_status == RasterStatus::kResubmit) {
292 resubmitted_layer_tree_ = std::move(layer_tree);
293 return raster_status;
294 }
295
296 if (persistent_cache->IsDumpingSkp() &&
297 persistent_cache->StoredNewShaders()) {
298 auto screenshot =
299 ScreenshotLastLayerTree(ScreenshotType::SkiaPicture, false);
300 persistent_cache->DumpSkp(*screenshot.data);
301 }
302
303 // TODO(liyuqian): in Fuchsia, the rasterization doesn't finish when
304 // Rasterizer::DoDraw finishes. Future work is needed to adapt the timestamp
305 // for Fuchsia to capture SceneUpdateContext::ExecutePaintTasks.
306 const auto raster_finish_time = fml::TimePoint::Now();
307 timing.Set(FrameTiming::kRasterFinish, raster_finish_time);
308 delegate_.OnFrameRasterized(timing);
309
310// SceneDisplayLag events are disabled on Fuchsia.
311// see: https://github.com/flutter/flutter/issues/56598
312#if !defined(OS_FUCHSIA)
313 if (raster_finish_time > frame_target_time) {
314 fml::TimePoint latest_frame_target_time =
315 delegate_.GetLatestFrameTargetTime();
316 const auto frame_budget_millis = delegate_.GetFrameBudget().count();
317 if (latest_frame_target_time < raster_finish_time) {
318 latest_frame_target_time =
319 latest_frame_target_time +
320 fml::TimeDelta::FromMillisecondsF(frame_budget_millis);
321 }
322 const auto frame_lag =
323 (latest_frame_target_time - frame_target_time).ToMillisecondsF();
324 const int vsync_transitions_missed = round(frame_lag / frame_budget_millis);
325 fml::tracing::TraceEventAsyncComplete(
326 "flutter", // category
327 "SceneDisplayLag", // name
328 raster_finish_time, // begin_time
329 latest_frame_target_time, // end_time
330 "frame_target_time", // arg_key_1
331 frame_target_time, // arg_val_1
332 "current_frame_target_time", // arg_key_2
333 latest_frame_target_time, // arg_val_2
334 "vsync_transitions_missed", // arg_key_3
335 vsync_transitions_missed // arg_val_3
336 );
337 }
338#endif
339
340 // Pipeline pressure is applied from a couple of places:
341 // rasterizer: When there are more items as of the time of Consume.
342 // animator (via shell): Frame gets produces every vsync.
343 // Enqueing here is to account for the following scenario:
344 // T = 1
345 // - one item (A) in the pipeline
346 // - rasterizer starts (and merges the threads)
347 // - pipeline consume result says no items to process
348 // T = 2
349 // - animator produces (B) to the pipeline
350 // - applies pipeline pressure via platform thread.
351 // T = 3
352 // - rasterizes finished (and un-merges the threads)
353 // - |Draw| for B yields as its on the wrong thread.
354 // This enqueue ensures that we attempt to consume from the right
355 // thread one more time after un-merge.
356 if (raster_thread_merger_) {
357 if (raster_thread_merger_->DecrementLease() ==
358 fml::RasterThreadStatus::kUnmergedNow) {
359 return RasterStatus::kEnqueuePipeline;
360 }
361 }
362
363 return raster_status;
364}
365
366RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
367 TRACE_EVENT0("flutter", "Rasterizer::DrawToSurface");
368 FML_DCHECK(surface_);
369
370 // There is no way for the compositor to know how long the layer tree
371 // construction took. Fortunately, the layer tree does. Grab that time
372 // for instrumentation.
373 compositor_context_->ui_time().SetLapTime(layer_tree.build_time());
374
375 auto* external_view_embedder = surface_->GetExternalViewEmbedder();
376
377 SkCanvas* embedder_root_canvas = nullptr;
378 if (external_view_embedder != nullptr) {
379 external_view_embedder->BeginFrame(
380 layer_tree.frame_size(), surface_->GetContext(),
381 layer_tree.device_pixel_ratio(), raster_thread_merger_);
382 embedder_root_canvas = external_view_embedder->GetRootCanvas();
383 }
384
385 // On Android, the external view embedder deletes surfaces in `BeginFrame`.
386 //
387 // Deleting a surface also clears the GL context. Therefore, acquire the
388 // frame after calling `BeginFrame` as this operation resets the GL context.
389 auto frame = surface_->AcquireFrame(layer_tree.frame_size());
390
391 if (frame == nullptr) {
392 return RasterStatus::kFailed;
393 }
394
395 // If the external view embedder has specified an optional root surface, the
396 // root surface transformation is set by the embedder instead of
397 // having to apply it here.
398 SkMatrix root_surface_transformation =
399 embedder_root_canvas ? SkMatrix{} : surface_->GetRootTransformation();
400
401 auto root_surface_canvas =
402 embedder_root_canvas ? embedder_root_canvas : frame->SkiaCanvas();
403
404 auto compositor_frame = compositor_context_->AcquireFrame(
405 surface_->GetContext(), // skia GrContext
406 root_surface_canvas, // root surface canvas
407 external_view_embedder, // external view embedder
408 root_surface_transformation, // root surface transformation
409 true, // instrumentation enabled
410 frame->supports_readback(), // surface supports pixel reads
411 raster_thread_merger_ // thread merger
412 );
413
414 if (compositor_frame) {
415 RasterStatus raster_status = compositor_frame->Raster(layer_tree, false);
416 if (raster_status == RasterStatus::kFailed) {
417 return raster_status;
418 }
419 if (external_view_embedder != nullptr) {
420 FML_DCHECK(!frame->IsSubmitted());
421 external_view_embedder->SubmitFrame(surface_->GetContext(),
422 std::move(frame));
423 } else {
424 frame->Submit();
425 }
426
427 FireNextFrameCallbackIfPresent();
428
429 if (surface_->GetContext()) {
430 TRACE_EVENT0("flutter", "PerformDeferredSkiaCleanup");
431 surface_->GetContext()->performDeferredCleanup(kSkiaCleanupExpiration);
432 }
433
434 return raster_status;
435 }
436
437 return RasterStatus::kFailed;
438}
439
440static sk_sp<SkData> ScreenshotLayerTreeAsPicture(
441 flutter::LayerTree* tree,
442 flutter::CompositorContext& compositor_context) {
443 FML_DCHECK(tree != nullptr);
444 SkPictureRecorder recorder;
445 recorder.beginRecording(
446 SkRect::MakeWH(tree->frame_size().width(), tree->frame_size().height()));
447
448 SkMatrix root_surface_transformation;
449 root_surface_transformation.reset();
450
451#if defined(LEGACY_FUCHSIA_EMBEDDER)
452 // TODO(arbreng: fxb/55805) Our ScopedFrame implementation doesnt do the
453 // right thing here so initialize the base class directly. This wont be
454 // needed after we move to using the embedder API on Fuchsia.
455 auto frame = std::make_unique<flutter::CompositorContext::ScopedFrame>(
456 compositor_context, nullptr, recorder.getRecordingCanvas(), nullptr,
457 root_surface_transformation, false, true, nullptr);
458#else
459 // TODO(amirh): figure out how to take a screenshot with embedded UIView.
460 // https://github.com/flutter/flutter/issues/23435
461 auto frame = compositor_context.AcquireFrame(
462 nullptr, recorder.getRecordingCanvas(), nullptr,
463 root_surface_transformation, false, true, nullptr);
464#endif // defined(LEGACY_FUCHSIA_EMBEDDER)
465
466 frame->Raster(*tree, true);
467
468#if defined(OS_FUCHSIA)
469 SkSerialProcs procs = {0};
470 procs.fImageProc = SerializeImageWithoutData;
471#else
472 SkSerialProcs procs = {0};
473 procs.fTypefaceProc = SerializeTypefaceWithData;
474#endif
475
476 return recorder.finishRecordingAsPicture()->serialize(&procs);
477}
478
479static sk_sp<SkSurface> CreateSnapshotSurface(GrDirectContext* surface_context,
480 const SkISize& size) {
481 const auto image_info = SkImageInfo::MakeN32Premul(
482 size.width(), size.height(), SkColorSpace::MakeSRGB());
483 if (surface_context) {
484 // There is a rendering surface that may contain textures that are going to
485 // be referenced in the layer tree about to be drawn.
486 return SkSurface::MakeRenderTarget(surface_context, //
487 SkBudgeted::kNo, //
488 image_info //
489 );
490 }
491
492 // There is no rendering surface, assume no GPU textures are present and
493 // create a raster surface.
494 return SkSurface::MakeRaster(image_info);
495}
496
497sk_sp<SkData> Rasterizer::ScreenshotLayerTreeAsImage(
498 flutter::LayerTree* tree,
499 flutter::CompositorContext& compositor_context,
500 GrDirectContext* surface_context,
501 bool compressed) {
502 // Attempt to create a snapshot surface depending on whether we have access to
503 // a valid GPU rendering context.
504 auto snapshot_surface =
505 CreateSnapshotSurface(surface_context, tree->frame_size());
506 if (snapshot_surface == nullptr) {
507 FML_LOG(ERROR) << "Screenshot: unable to create snapshot surface";
508 return nullptr;
509 }
510
511 // Draw the current layer tree into the snapshot surface.
512 auto* canvas = snapshot_surface->getCanvas();
513
514 // There is no root surface transformation for the screenshot layer. Reset the
515 // matrix to identity.
516 SkMatrix root_surface_transformation;
517 root_surface_transformation.reset();
518
519 // We want to ensure we call the base method for
520 // CompositorContext::AcquireFrame instead of the platform-specific method.
521 // Specifically, Fuchsia's CompositorContext handles the rendering surface
522 // itself which means that we will still continue to render to the onscreen
523 // surface if we don't call the base method.
524 auto frame = compositor_context.flutter::CompositorContext::AcquireFrame(
525 surface_context, canvas, nullptr, root_surface_transformation, false,
526 true, nullptr);
527 canvas->clear(SK_ColorTRANSPARENT);
528 frame->Raster(*tree, true);
529 canvas->flush();
530
531 // snapshot_surface->makeImageSnapshot needs the GL context to be set if the
532 // render context is GL. frame->Raster() pops the gl context in platforms that
533 // gl context switching are used. (For example, older iOS that uses GL) We
534 // reset the GL context using the context switch.
535 auto context_switch = surface_->MakeRenderContextCurrent();
536 if (!context_switch->GetResult()) {
537 FML_LOG(ERROR) << "Screenshot: unable to make image screenshot";
538 return nullptr;
539 }
540 // Prepare an image from the surface, this image may potentially be on th GPU.
541 auto potentially_gpu_snapshot = snapshot_surface->makeImageSnapshot();
542 if (!potentially_gpu_snapshot) {
543 FML_LOG(ERROR) << "Screenshot: unable to make image screenshot";
544 return nullptr;
545 }
546
547 // Copy the GPU image snapshot into CPU memory.
548 auto cpu_snapshot = potentially_gpu_snapshot->makeRasterImage();
549 if (!cpu_snapshot) {
550 FML_LOG(ERROR) << "Screenshot: unable to make raster image";
551 return nullptr;
552 }
553
554 // If the caller want the pixels to be compressed, there is a Skia utility to
555 // compress to PNG. Use that.
556 if (compressed) {
557 return cpu_snapshot->encodeToData();
558 }
559
560 // Copy it into a bitmap and return the same.
561 SkPixmap pixmap;
562 if (!cpu_snapshot->peekPixels(&pixmap)) {
563 FML_LOG(ERROR) << "Screenshot: unable to obtain bitmap pixels";
564 return nullptr;
565 }
566 return SkData::MakeWithCopy(pixmap.addr32(), pixmap.computeByteSize());
567}
568
569Rasterizer::Screenshot Rasterizer::ScreenshotLastLayerTree(
570 Rasterizer::ScreenshotType type,
571 bool base64_encode) {
572 auto* layer_tree = GetLastLayerTree();
573 if (layer_tree == nullptr) {
574 FML_LOG(ERROR) << "Last layer tree was null when screenshotting.";
575 return {};
576 }
577
578 sk_sp<SkData> data = nullptr;
579
580 GrDirectContext* surface_context =
581 surface_ ? surface_->GetContext() : nullptr;
582
583 switch (type) {
584 case ScreenshotType::SkiaPicture:
585 data = ScreenshotLayerTreeAsPicture(layer_tree, *compositor_context_);
586 break;
587 case ScreenshotType::UncompressedImage:
588 data = ScreenshotLayerTreeAsImage(layer_tree, *compositor_context_,
589 surface_context, false);
590 break;
591 case ScreenshotType::CompressedImage:
592 data = ScreenshotLayerTreeAsImage(layer_tree, *compositor_context_,
593 surface_context, true);
594 break;
595 }
596
597 if (data == nullptr) {
598 FML_LOG(ERROR) << "Screenshot data was null.";
599 return {};
600 }
601
602 if (base64_encode) {
603 size_t b64_size = SkBase64::Encode(data->data(), data->size(), nullptr);
604 auto b64_data = SkData::MakeUninitialized(b64_size);
605 SkBase64::Encode(data->data(), data->size(), b64_data->writable_data());
606 return Rasterizer::Screenshot{b64_data, layer_tree->frame_size()};
607 }
608
609 return Rasterizer::Screenshot{data, layer_tree->frame_size()};
610}
611
612void Rasterizer::SetNextFrameCallback(const fml::closure& callback) {
613 next_frame_callback_ = callback;
614}
615
616void Rasterizer::FireNextFrameCallbackIfPresent() {
617 if (!next_frame_callback_) {
618 return;
619 }
620 // It is safe for the callback to set a new callback.
621 auto callback = next_frame_callback_;
622 next_frame_callback_ = nullptr;
623 callback();
624}
625
626void Rasterizer::SetResourceCacheMaxBytes(size_t max_bytes, bool from_user) {
627 user_override_resource_cache_bytes_ |= from_user;
628
629 if (!from_user && user_override_resource_cache_bytes_) {
630 // We should not update the setting here if a user has explicitly set a
631 // value for this over the flutter/skia channel.
632 return;
633 }
634
635 max_cache_bytes_ = max_bytes;
636 if (!surface_) {
637 return;
638 }
639
640 GrDirectContext* context = surface_->GetContext();
641 if (context) {
642 int max_resources;
643 context->getResourceCacheLimits(&max_resources, nullptr);
644 context->setResourceCacheLimits(max_resources, max_bytes);
645 }
646}
647
648std::optional<size_t> Rasterizer::GetResourceCacheMaxBytes() const {
649 if (!surface_) {
650 return std::nullopt;
651 }
652 GrDirectContext* context = surface_->GetContext();
653 if (context) {
654 size_t max_bytes;
655 context->getResourceCacheLimits(nullptr, &max_bytes);
656 return max_bytes;
657 }
658 return std::nullopt;
659}
660
661Rasterizer::Screenshot::Screenshot() {}
662
663Rasterizer::Screenshot::Screenshot(sk_sp<SkData> p_data, SkISize p_size)
664 : data(std::move(p_data)), frame_size(p_size) {}
665
666Rasterizer::Screenshot::Screenshot(const Screenshot& other) = default;
667
668Rasterizer::Screenshot::~Screenshot() = default;
669
670} // namespace flutter
671