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 | |
22 | namespace flutter { |
23 | |
24 | // The rasterizer will tell Skia to purge cached resources that have not been |
25 | // used within this interval. |
26 | static constexpr std::chrono::milliseconds kSkiaCleanupExpiration(15000); |
27 | |
28 | Rasterizer::Rasterizer(Delegate& delegate) |
29 | : Rasterizer(delegate, |
30 | std::make_unique<flutter::CompositorContext>( |
31 | delegate.GetFrameBudget())) {} |
32 | |
33 | Rasterizer::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 | |
43 | Rasterizer::~Rasterizer() = default; |
44 | |
45 | fml::TaskRunnerAffineWeakPtr<Rasterizer> Rasterizer::GetWeakPtr() const { |
46 | return weak_factory_.GetWeakPtr(); |
47 | } |
48 | |
49 | fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> Rasterizer::GetSnapshotDelegate() |
50 | const { |
51 | return weak_factory_.GetWeakPtr(); |
52 | } |
53 | |
54 | void 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 | |
75 | void Rasterizer::Teardown() { |
76 | compositor_context_->OnGrContextDestroyed(); |
77 | surface_.reset(); |
78 | last_layer_tree_.reset(); |
79 | } |
80 | |
81 | void 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 | |
96 | flutter::TextureRegistry* Rasterizer::GetTextureRegistry() { |
97 | return &compositor_context_->texture_registry(); |
98 | } |
99 | |
100 | flutter::LayerTree* Rasterizer::GetLastLayerTree() { |
101 | return last_layer_tree_.get(); |
102 | } |
103 | |
104 | void Rasterizer::DrawLastLayerTree() { |
105 | if (!last_layer_tree_ || !surface_) { |
106 | return; |
107 | } |
108 | DrawToSurface(*last_layer_tree_); |
109 | } |
110 | |
111 | void 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 | |
167 | namespace { |
168 | sk_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 | |
199 | sk_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 | |
238 | sk_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 | |
246 | sk_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 | |
266 | RasterStatus 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 | |
366 | RasterStatus 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 | |
440 | static 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 | |
479 | static 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 | |
497 | sk_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 | |
569 | Rasterizer::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 | |
612 | void Rasterizer::SetNextFrameCallback(const fml::closure& callback) { |
613 | next_frame_callback_ = callback; |
614 | } |
615 | |
616 | void 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 | |
626 | void 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 | |
648 | std::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 | |
661 | Rasterizer::Screenshot::Screenshot() {} |
662 | |
663 | Rasterizer::Screenshot::Screenshot(sk_sp<SkData> p_data, SkISize p_size) |
664 | : data(std::move(p_data)), frame_size(p_size) {} |
665 | |
666 | Rasterizer::Screenshot::Screenshot(const Screenshot& other) = default; |
667 | |
668 | Rasterizer::Screenshot::~Screenshot() = default; |
669 | |
670 | } // namespace flutter |
671 | |