Flutter Engine
The Flutter Engine
multi_frame_codec.cc
Go to the documentation of this file.
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
5#include "flutter/lib/ui/painting/multi_frame_codec.h"
6
7#include <utility>
8
9#include "flutter/fml/make_copyable.h"
10#include "flutter/lib/ui/painting/display_list_image_gpu.h"
11#include "flutter/lib/ui/painting/image.h"
12#if IMPELLER_SUPPORTS_RENDERING
13#include "flutter/lib/ui/painting/image_decoder_impeller.h"
14#endif // IMPELLER_SUPPORTS_RENDERING
15#include "third_party/dart/runtime/include/dart_api.h"
21
22namespace flutter {
23
24MultiFrameCodec::MultiFrameCodec(std::shared_ptr<ImageGenerator> generator)
25 : state_(new State(std::move(generator))) {}
26
28
29MultiFrameCodec::State::State(std::shared_ptr<ImageGenerator> generator)
30 : generator_(std::move(generator)),
31 frameCount_(generator_->GetFrameCount()),
32 repetitionCount_(generator_->GetPlayCount() ==
33 ImageGenerator::kInfinitePlayCount
34 ? -1
35 : generator_->GetPlayCount() - 1),
36 is_impeller_enabled_(UIDartState::Current()->IsImpellerEnabled()) {}
37
40 int duration,
41 const std::string& decode_error,
42 std::unique_ptr<tonic::DartPersistentValue> callback,
43 size_t trace_id) {
44 std::shared_ptr<tonic::DartState> dart_state = callback->dart_state().lock();
45 if (!dart_state) {
46 FML_DLOG(ERROR) << "Could not acquire Dart state while attempting to fire "
47 "next frame callback.";
48 return;
49 }
50 tonic::DartState::Scope scope(dart_state);
52 {tonic::ToDart(image), tonic::ToDart(duration),
53 tonic::ToDart(decode_error)});
54}
55
56std::pair<sk_sp<DlImage>, std::string>
57MultiFrameCodec::State::GetNextFrameImage(
58 fml::WeakPtr<GrDirectContext> resourceContext,
59 const std::shared_ptr<const fml::SyncSwitch>& gpu_disable_sync_switch,
60 const std::shared_ptr<impeller::Context>& impeller_context,
63 SkImageInfo info = generator_->GetInfo().makeColorType(kN32_SkColorType);
64 if (info.alphaType() == kUnpremul_SkAlphaType) {
65 SkImageInfo updated = info.makeAlphaType(kPremul_SkAlphaType);
66 info = updated;
67 }
68 if (!bitmap.tryAllocPixels(info)) {
69 std::ostringstream ostr;
70 ostr << "Failed to allocate memory for bitmap of size "
71 << info.computeMinByteSize() << "B";
72 std::string decode_error = ostr.str();
73 FML_LOG(ERROR) << decode_error;
74 return std::make_pair(nullptr, decode_error);
75 }
76
77 ImageGenerator::FrameInfo frameInfo =
78 generator_->GetFrameInfo(nextFrameIndex_);
79
80 const int requiredFrameIndex =
81 frameInfo.required_frame.value_or(SkCodec::kNoFrame);
82
83 if (requiredFrameIndex != SkCodec::kNoFrame) {
84 // We are here when the frame said |disposal_method| is
85 // `DisposalMethod::kKeep` or `DisposalMethod::kRestorePrevious` and
86 // |requiredFrameIndex| is set to ex-frame or ex-ex-frame.
87 if (!lastRequiredFrame_.has_value()) {
88 FML_DLOG(INFO)
89 << "Frame " << nextFrameIndex_ << " depends on frame "
90 << requiredFrameIndex
91 << " and no required frames are cached. Using blank slate instead.";
92 } else {
93 // Copy the previous frame's output buffer into the current frame as the
94 // starting point.
95 bitmap.writePixels(lastRequiredFrame_->pixmap());
96 if (restoreBGColorRect_.has_value()) {
97 bitmap.erase(SK_ColorTRANSPARENT, restoreBGColorRect_.value());
98 }
99 }
100 }
101
102 // Write the new frame to the output buffer. The bitmap pixels as supplied
103 // are already set in accordance with the previous frame's disposal policy.
104 if (!generator_->GetPixels(info, bitmap.getPixels(), bitmap.rowBytes(),
105 nextFrameIndex_, requiredFrameIndex)) {
106 std::ostringstream ostr;
107 ostr << "Could not getPixels for frame " << nextFrameIndex_;
108 std::string decode_error = ostr.str();
109 FML_LOG(ERROR) << decode_error;
110 return std::make_pair(nullptr, decode_error);
111 }
112
113 const bool keep_current_frame =
114 frameInfo.disposal_method == SkCodecAnimation::DisposalMethod::kKeep;
115 const bool restore_previous_frame =
116 frameInfo.disposal_method ==
118 const bool previous_frame_available = lastRequiredFrame_.has_value();
119
120 // Store the current frame in `lastRequiredFrame_` if the frame's disposal
121 // method indicates we should do so.
122 // * When the disposal method is "Keep", the stored frame should always be
123 // overwritten with the new frame we just crafted.
124 // * When the disposal method is "RestorePrevious", the previously stored
125 // frame should be retained and used as the backdrop for the next frame
126 // again. If there isn't already a stored frame, that means we haven't
127 // rendered any frames yet! When this happens, we just fall back to "Keep"
128 // behavior and store the current frame as the backdrop of the next frame.
129
130 if (keep_current_frame ||
131 (previous_frame_available && !restore_previous_frame)) {
132 // Replace the stored frame. The `lastRequiredFrame_` will get used as the
133 // starting backdrop for the next frame.
134 lastRequiredFrame_ = bitmap;
135 lastRequiredFrameIndex_ = nextFrameIndex_;
136 }
137
138 if (frameInfo.disposal_method ==
140 restoreBGColorRect_ = frameInfo.disposal_rect;
141 } else {
142 restoreBGColorRect_.reset();
143 }
144
145#if IMPELLER_SUPPORTS_RENDERING
146 if (is_impeller_enabled_) {
147 // This is safe regardless of whether the GPU is available or not because
148 // without mipmap creation there is no command buffer encoding done.
150 impeller_context, std::make_shared<SkBitmap>(bitmap),
151 std::make_shared<fml::SyncSwitch>(),
153 /*create_mips=*/false);
154 }
155#endif // IMPELLER_SUPPORTS_RENDERING
156
157#if !SLIMPELLER
158 sk_sp<SkImage> skImage;
159 gpu_disable_sync_switch->Execute(
161 .SetIfTrue([&skImage, &bitmap] {
162 // Defer decoding until time of draw later on the raster thread.
163 // Can happen when GL operations are currently forbidden such as
164 // in the background on iOS.
166 })
167 .SetIfFalse([&skImage, &resourceContext, &bitmap] {
168 if (resourceContext) {
169 SkPixmap pixmap(bitmap.info(), bitmap.pixelRef()->pixels(),
170 bitmap.pixelRef()->rowBytes());
172 resourceContext.get(), pixmap, true);
173 } else {
174 // Defer decoding until time of draw later on the raster thread.
175 // Can happen when GL operations are currently forbidden such as
176 // in the background on iOS.
178 }
179 }));
180
181 return std::make_pair(DlImageGPU::Make({skImage, std::move(unref_queue)}),
182 std::string());
183#else // !SLIMPELLER
184 return std::make_pair(nullptr, "Unsupported backend.");
185#endif // !SLIMPELLER
186}
187
188void MultiFrameCodec::State::GetNextFrameAndInvokeCallback(
189 std::unique_ptr<tonic::DartPersistentValue> callback,
190 const fml::RefPtr<fml::TaskRunner>& ui_task_runner,
191 fml::WeakPtr<GrDirectContext> resourceContext,
193 const std::shared_ptr<const fml::SyncSwitch>& gpu_disable_sync_switch,
194 size_t trace_id,
195 const std::shared_ptr<impeller::Context>& impeller_context) {
197 int duration = 0;
198 sk_sp<DlImage> dlImage;
199 std::string decode_error;
200 std::tie(dlImage, decode_error) =
201 GetNextFrameImage(std::move(resourceContext), gpu_disable_sync_switch,
202 impeller_context, std::move(unref_queue));
203 if (dlImage) {
205 image->set_image(dlImage);
206 ImageGenerator::FrameInfo frameInfo =
207 generator_->GetFrameInfo(nextFrameIndex_);
208 duration = frameInfo.duration;
209 }
210 nextFrameIndex_ = (nextFrameIndex_ + 1) % frameCount_;
211
212 // The static leak checker gets confused by the use of fml::MakeCopyable.
213 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
214 ui_task_runner->PostTask(fml::MakeCopyable(
215 [callback = std::move(callback), image = std::move(image),
216 decode_error = std::move(decode_error), duration, trace_id]() mutable {
218 std::move(callback), trace_id);
219 }));
220}
221
223 static size_t trace_counter = 1;
224 const size_t trace_id = trace_counter++;
225
226 if (!Dart_IsClosure(callback_handle)) {
227 return tonic::ToDart("Callback must be a function");
228 }
229
230 auto* dart_state = UIDartState::Current();
231
232 const auto& task_runners = dart_state->GetTaskRunners();
233
234 if (state_->frameCount_ == 0) {
235 std::string decode_error("Could not provide any frame.");
236 FML_LOG(ERROR) << decode_error;
237 task_runners.GetUITaskRunner()->PostTask(fml::MakeCopyable(
238 [trace_id, decode_error = std::move(decode_error),
239 callback = std::make_unique<tonic::DartPersistentValue>(
240 tonic::DartState::Current(), callback_handle)]() mutable {
241 InvokeNextFrameCallback(nullptr, 0, decode_error, std::move(callback),
242 trace_id);
243 }));
244 return Dart_Null();
245 }
246
247 task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable(
248 [callback = std::make_unique<tonic::DartPersistentValue>(
249 tonic::DartState::Current(), callback_handle),
250 weak_state = std::weak_ptr<MultiFrameCodec::State>(state_), trace_id,
251 ui_task_runner = task_runners.GetUITaskRunner(),
252 io_manager = dart_state->GetIOManager()]() mutable {
253 auto state = weak_state.lock();
254 if (!state) {
255 ui_task_runner->PostTask(fml::MakeCopyable(
256 [callback = std::move(callback)]() { callback->Clear(); }));
257 return;
258 }
259 state->GetNextFrameAndInvokeCallback(
260 std::move(callback), ui_task_runner,
261 io_manager->GetResourceContext(), io_manager->GetSkiaUnrefQueue(),
262 io_manager->GetIsGpuDisabledSyncSwitch(), trace_id,
263 io_manager->GetImpellerContext());
264 }));
265
266 return Dart_Null();
267 // The static leak checker gets confused by the control flow, unique
268 // pointers and closures in this function.
269 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
270}
271
272int MultiFrameCodec::frameCount() const {
273 return state_->frameCount_;
274}
275
276int MultiFrameCodec::repetitionCount() const {
277 return state_->repetitionCount_;
278}
279
280} // namespace flutter
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
kUnpremul_SkAlphaType
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition: SkAlphaType.h:29
constexpr SkColor SK_ColorTRANSPARENT
Definition: SkColor.h:99
static constexpr int kNoFrame
Definition: SkCodec.h:650
static fml::RefPtr< CanvasImage > Create()
Definition: image.h:28
static sk_sp< DlImageGPU > Make(SkiaGPUObject< SkImage > image)
static std::pair< sk_sp< DlImage >, std::string > UploadTextureToStorage(const std::shared_ptr< impeller::Context > &context, std::shared_ptr< SkBitmap > bitmap, const std::shared_ptr< fml::SyncSwitch > &gpu_disabled_switch, impeller::StorageMode storage_mode, bool create_mips=true)
Create a host visible texture from the provided bitmap.
The minimal interface necessary for defining a decoder that can be used for both single and multi-fra...
MultiFrameCodec(std::shared_ptr< ImageGenerator > generator)
Dart_Handle getNextFrame(Dart_Handle args) override
static UIDartState * Current()
virtual void PostTask(const fml::closure &task) override
Definition: task_runner.cc:24
T * get() const
Definition: weak_ptr.h:87
static DartState * Current()
Definition: dart_state.cc:56
struct _Dart_Handle * Dart_Handle
Definition: dart_api.h:258
DART_EXPORT Dart_Handle Dart_Null(void)
DART_EXPORT bool Dart_IsClosure(Dart_Handle object)
double duration
Definition: examples.cpp:30
AtkStateType state
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
#define FML_DLOG(severity)
Definition: logging.h:102
#define FML_LOG(severity)
Definition: logging.h:82
SK_API sk_sp< SkImage > CrossContextTextureFromPixmap(GrDirectContext *context, const SkPixmap &pixmap, bool buildMips, bool limitToMaxTextureSize=false)
SK_API sk_sp< SkImage > RasterFromBitmap(const SkBitmap &bitmap)
sk_sp< const SkImage > image
Definition: SkRecords.h:269
Definition: bitmap.py:1
static void InvokeNextFrameCallback(const fml::RefPtr< CanvasImage > &image, int duration, const std::string &decode_error, std::unique_ptr< tonic::DartPersistentValue > callback, size_t trace_id)
internal::CopyableLambda< T > MakeCopyable(T lambda)
Definition: make_copyable.h:57
Definition: ref_ptr.h:256
Dart_Handle ToDart(const T &object)
Dart_Handle DartInvoke(Dart_Handle closure, std::initializer_list< Dart_Handle > args)
Definition: dart_invoke.cc:20
Represents the 2 code paths available when calling |SyncSwitch::Execute|.
Definition: sync_switch.h:35
#define ERROR(message)
Definition: elf_loader.cc:260