Flutter Engine
 
Loading...
Searching...
No Matches
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
6
7#include <utility>
8
13#if IMPELLER_SUPPORTS_RENDERING
16#endif // IMPELLER_SUPPORTS_RENDERING
17#include "third_party/dart/runtime/include/dart_api.h"
18#include "third_party/skia/include/codec/SkCodecAnimation.h"
19#include "third_party/skia/include/core/SkImage.h"
20#include "third_party/skia/include/core/SkPixelRef.h"
21#include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h"
23
24namespace flutter {
25
26MultiFrameCodec::MultiFrameCodec(std::shared_ptr<ImageGenerator> generator)
27 : state_(new State(std::move(generator))) {}
28
30
31MultiFrameCodec::State::State(std::shared_ptr<ImageGenerator> generator)
32 : generator_(std::move(generator)),
33 frameCount_(generator_->GetFrameCount()),
34 repetitionCount_(generator_->GetPlayCount() ==
35 ImageGenerator::kInfinitePlayCount
36 ? -1
37 : generator_->GetPlayCount() - 1),
38 is_impeller_enabled_(UIDartState::Current()->IsImpellerEnabled()) {}
39
42 int duration,
43 const std::string& decode_error,
44 std::unique_ptr<tonic::DartPersistentValue> callback,
45 size_t trace_id) {
46 std::shared_ptr<tonic::DartState> dart_state = callback->dart_state().lock();
47 if (!dart_state) {
48 FML_DLOG(ERROR) << "Could not acquire Dart state while attempting to fire "
49 "next frame callback.";
50 return;
51 }
52 tonic::DartState::Scope scope(dart_state);
54 {tonic::ToDart(image), tonic::ToDart(duration),
55 tonic::ToDart(decode_error)});
56}
57
58std::pair<sk_sp<DlImage>, std::string>
59MultiFrameCodec::State::GetNextFrameImage(
60 const fml::WeakPtr<GrDirectContext>& resourceContext,
61 const std::shared_ptr<const fml::SyncSwitch>& gpu_disable_sync_switch,
62 const std::shared_ptr<impeller::Context>& impeller_context,
63 const fml::RefPtr<flutter::SkiaUnrefQueue>& unref_queue) {
64 SkBitmap bitmap = SkBitmap();
65 SkImageInfo info = generator_->GetInfo().makeColorType(kN32_SkColorType);
66 if (info.alphaType() == kUnpremul_SkAlphaType) {
67 SkImageInfo updated = info.makeAlphaType(kPremul_SkAlphaType);
68 info = updated;
69 }
70 if (!bitmap.tryAllocPixels(info)) {
71 std::ostringstream ostr;
72 ostr << "Failed to allocate memory for bitmap of size "
73 << info.computeMinByteSize() << "B";
74 std::string decode_error = ostr.str();
75 FML_LOG(ERROR) << decode_error;
76 return std::make_pair(nullptr, decode_error);
77 }
78
79 ImageGenerator::FrameInfo frameInfo =
80 generator_->GetFrameInfo(nextFrameIndex_);
81
82 const int requiredFrameIndex =
83 frameInfo.required_frame.value_or(SkCodec::kNoFrame);
84
85 if (requiredFrameIndex != SkCodec::kNoFrame) {
86 // We are here when the frame said |disposal_method| is
87 // `DisposalMethod::kKeep` or `DisposalMethod::kRestorePrevious` and
88 // |requiredFrameIndex| is set to ex-frame or ex-ex-frame.
89 if (!lastRequiredFrame_.has_value()) {
90 FML_DLOG(INFO)
91 << "Frame " << nextFrameIndex_ << " depends on frame "
92 << requiredFrameIndex
93 << " and no required frames are cached. Using blank slate instead.";
94 } else {
95 // Copy the previous frame's output buffer into the current frame as the
96 // starting point.
97 bitmap.writePixels(lastRequiredFrame_->pixmap());
98 if (restoreBGColorRect_.has_value()) {
99 bitmap.erase(SK_ColorTRANSPARENT, restoreBGColorRect_.value());
100 }
101 }
102 }
103
104 // Write the new frame to the output buffer. The bitmap pixels as supplied
105 // are already set in accordance with the previous frame's disposal policy.
106 if (!generator_->GetPixels(info, bitmap.getPixels(), bitmap.rowBytes(),
107 nextFrameIndex_, requiredFrameIndex)) {
108 std::ostringstream ostr;
109 ostr << "Could not getPixels for frame " << nextFrameIndex_;
110 std::string decode_error = ostr.str();
111 FML_LOG(ERROR) << decode_error;
112 return std::make_pair(nullptr, decode_error);
113 }
114
115 const bool keep_current_frame =
116 frameInfo.disposal_method == SkCodecAnimation::DisposalMethod::kKeep;
117 const bool restore_previous_frame =
118 frameInfo.disposal_method ==
119 SkCodecAnimation::DisposalMethod::kRestorePrevious;
120 const bool previous_frame_available = lastRequiredFrame_.has_value();
121
122 // Store the current frame in `lastRequiredFrame_` if the frame's disposal
123 // method indicates we should do so.
124 // * When the disposal method is "Keep", the stored frame should always be
125 // overwritten with the new frame we just crafted.
126 // * When the disposal method is "RestorePrevious", the previously stored
127 // frame should be retained and used as the backdrop for the next frame
128 // again. If there isn't already a stored frame, that means we haven't
129 // rendered any frames yet! When this happens, we just fall back to "Keep"
130 // behavior and store the current frame as the backdrop of the next frame.
131
132 if (keep_current_frame ||
133 (previous_frame_available && !restore_previous_frame)) {
134 // Replace the stored frame. The `lastRequiredFrame_` will get used as the
135 // starting backdrop for the next frame.
136 lastRequiredFrame_ = bitmap;
137 lastRequiredFrameIndex_ = nextFrameIndex_;
138 }
139
140 if (frameInfo.disposal_method ==
141 SkCodecAnimation::DisposalMethod::kRestoreBGColor) {
142 restoreBGColorRect_ = frameInfo.disposal_rect;
143 } else {
144 restoreBGColorRect_.reset();
145 }
146
147#if IMPELLER_SUPPORTS_RENDERING
148 if (is_impeller_enabled_) {
149#ifdef FML_OS_IOS
150 // This is safe regardless of whether the GPU is available or not because
151 // without mipmap creation there is no command buffer encoding done.
153 impeller_context, std::make_shared<SkBitmap>(bitmap));
154#else
155 sk_sp<DlImage> dl_image;
156 std::string error_message;
157 auto mapping = std::make_unique<fml::NonOwnedMapping>(
158 reinterpret_cast<const uint8_t*>(bitmap.getAddr(0, 0)), // data
159 bitmap.dimensions().area() * info.bytesPerPixel(), // size
160 [bitmap](auto, auto) mutable { bitmap.reset(); } // proc
161 );
162 std::shared_ptr<impeller::DeviceBuffer> device_buffer =
163 impeller_context->GetResourceAllocator()->CreateBufferWithCopy(
164 *mapping);
165 if (!device_buffer) {
166 return std::make_pair(nullptr, "Failed to allocate staging buffer.");
167 }
168
170 [&](sk_sp<DlImage> image, std::string message) {
171 dl_image = std::move(image);
172 error_message = std::move(message);
173 },
174 impeller_context, device_buffer, info,
175 std::make_shared<SkBitmap>(bitmap), std::nullopt,
176 gpu_disable_sync_switch);
177 return std::make_pair(dl_image, error_message);
178#endif
179 }
180#endif // IMPELLER_SUPPORTS_RENDERING
181
182#if !SLIMPELLER
183 sk_sp<SkImage> skImage;
184 gpu_disable_sync_switch->Execute(
186 .SetIfTrue([&skImage, &bitmap] {
187 // Defer decoding until time of draw later on the raster thread.
188 // Can happen when GL operations are currently forbidden such as
189 // in the background on iOS.
190 skImage = SkImages::RasterFromBitmap(bitmap);
191 })
192 .SetIfFalse([&skImage, &resourceContext, &bitmap] {
193 if (resourceContext) {
194 SkPixmap pixmap(bitmap.info(), bitmap.pixelRef()->pixels(),
195 bitmap.pixelRef()->rowBytes());
196 skImage = SkImages::CrossContextTextureFromPixmap(
197 resourceContext.get(), pixmap, true);
198 } else {
199 // Defer decoding until time of draw later on the raster thread.
200 // Can happen when GL operations are currently forbidden such as
201 // in the background on iOS.
202 skImage = SkImages::RasterFromBitmap(bitmap);
203 }
204 }));
205
206 return std::make_pair(DlImageGPU::Make({skImage, unref_queue}),
207 std::string());
208#else // !SLIMPELLER
209 return std::make_pair(nullptr, "Unsupported backend.");
210#endif // !SLIMPELLER
211}
212
213void MultiFrameCodec::State::GetNextFrameAndInvokeCallback(
214 std::unique_ptr<tonic::DartPersistentValue> callback,
215 const fml::RefPtr<fml::TaskRunner>& ui_task_runner,
216 const fml::WeakPtr<GrDirectContext>& resourceContext,
217 const fml::RefPtr<flutter::SkiaUnrefQueue>& unref_queue,
218 const std::shared_ptr<const fml::SyncSwitch>& gpu_disable_sync_switch,
219 size_t trace_id,
220 const std::shared_ptr<impeller::Context>& impeller_context) {
221#if FML_OS_IOS_SIMULATOR
222 // Noop backend.
223 if (!resourceContext && !impeller_context) {
224 ui_task_runner->PostTask(
225 fml::MakeCopyable([callback = std::move(callback)]() {
226 // must be destroyed on UI thread.
227 }));
228 return;
229 }
230#endif // FML_OS_IOS_SIMULATOR
231
233 int duration = 0;
234 sk_sp<DlImage> dlImage;
235 std::string decode_error;
236 std::tie(dlImage, decode_error) = GetNextFrameImage(
237 resourceContext, gpu_disable_sync_switch, impeller_context, unref_queue);
238 if (dlImage) {
240 image->set_image(dlImage);
241 ImageGenerator::FrameInfo frameInfo =
242 generator_->GetFrameInfo(nextFrameIndex_);
243 duration = frameInfo.duration;
244 }
245 nextFrameIndex_ = (nextFrameIndex_ + 1) % frameCount_;
246
247 // The static leak checker gets confused by the use of fml::MakeCopyable.
248 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
249 ui_task_runner->PostTask(fml::MakeCopyable(
250 [callback = std::move(callback), image = std::move(image),
251 decode_error = std::move(decode_error), duration, trace_id]() mutable {
252 InvokeNextFrameCallback(image, duration, decode_error,
253 std::move(callback), trace_id);
254 }));
255}
256
257Dart_Handle MultiFrameCodec::getNextFrame(Dart_Handle callback_handle) {
258 static size_t trace_counter = 1;
259 const size_t trace_id = trace_counter++;
260
261 if (!Dart_IsClosure(callback_handle)) {
262 return tonic::ToDart("Callback must be a function");
263 }
264
265 auto* dart_state = UIDartState::Current();
266
267 const auto& task_runners = dart_state->GetTaskRunners();
268
269 if (state_->frameCount_ == 0) {
270 std::string decode_error("Could not provide any frame.");
271 FML_LOG(ERROR) << decode_error;
272 task_runners.GetUITaskRunner()->PostTask(fml::MakeCopyable(
273 [trace_id, decode_error = std::move(decode_error),
274 callback = std::make_unique<tonic::DartPersistentValue>(
275 tonic::DartState::Current(), callback_handle)]() mutable {
276 InvokeNextFrameCallback(nullptr, 0, decode_error, std::move(callback),
277 trace_id);
278 }));
279 return Dart_Null();
280 }
281
282 task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable(
283 [callback = std::make_unique<tonic::DartPersistentValue>(
284 tonic::DartState::Current(), callback_handle),
285 weak_state = std::weak_ptr<MultiFrameCodec::State>(state_), trace_id,
286 ui_task_runner = task_runners.GetUITaskRunner(),
287 io_manager = dart_state->GetIOManager()]() mutable {
288 auto state = weak_state.lock();
289 if (!state) {
290 ui_task_runner->PostTask(fml::MakeCopyable(
291 [callback = std::move(callback)]() { callback->Clear(); }));
292 return;
293 }
294 state->GetNextFrameAndInvokeCallback(
295 std::move(callback), ui_task_runner,
296 io_manager->GetResourceContext(), io_manager->GetSkiaUnrefQueue(),
297 io_manager->GetIsGpuDisabledSyncSwitch(), trace_id,
298 io_manager->GetImpellerContext());
299 }));
300
301 return Dart_Null();
302 // The static leak checker gets confused by the control flow, unique
303 // pointers and closures in this function.
304 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
305}
306
307int MultiFrameCodec::frameCount() const {
308 return state_->frameCount_;
309}
310
311int MultiFrameCodec::repetitionCount() const {
312 return state_->repetitionCount_;
313}
314
315} // namespace flutter
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)
Create a texture from the provided bitmap.
static void UploadTextureToPrivate(ImageResult result, const std::shared_ptr< impeller::Context > &context, const std::shared_ptr< impeller::DeviceBuffer > &buffer, const SkImageInfo &image_info, const std::shared_ptr< SkBitmap > &bitmap, const std::optional< SkImageInfo > &resize_info, const std::shared_ptr< const fml::SyncSwitch > &gpu_disabled_switch)
Create a device private texture from the provided host buffer.
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()
T * get() const
Definition weak_ptr.h:87
static DartState * Current()
Definition dart_state.cc:56
FlutterVulkanImage * image
G_BEGIN_DECLS GBytes * message
FlutterDesktopBinaryReply callback
#define FML_DLOG(severity)
Definition logging.h:121
#define FML_LOG(severity)
Definition logging.h:101
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 ref_ptr.h:261
Dart_Handle ToDart(const T &object)
Dart_Handle DartInvoke(Dart_Handle closure, std::initializer_list< Dart_Handle > args)
Represents the 2 code paths available when calling |SyncSwitchExecute|.
Definition sync_switch.h:35