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/image_decoder_skia.h" |
6 | |
7 | #include <algorithm> |
8 | |
9 | #include "flutter/fml/logging.h" |
10 | #include "flutter/fml/make_copyable.h" |
11 | #include "flutter/lib/ui/painting/display_list_image_gpu.h" |
12 | #include "third_party/skia/include/core/SkBitmap.h" |
13 | #include "third_party/skia/include/core/SkImage.h" |
14 | #include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h" |
15 | |
16 | namespace flutter { |
17 | |
18 | ImageDecoderSkia::ImageDecoderSkia( |
19 | const TaskRunners& runners, |
20 | std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner, |
21 | fml::WeakPtr<IOManager> io_manager) |
22 | : ImageDecoder(runners, |
23 | std::move(concurrent_task_runner), |
24 | std::move(io_manager)) {} |
25 | |
26 | ImageDecoderSkia::~ImageDecoderSkia() = default; |
27 | |
28 | static sk_sp<SkImage> ResizeRasterImage(const sk_sp<SkImage>& image, |
29 | const SkISize& resized_dimensions, |
30 | const fml::tracing::TraceFlow& flow) { |
31 | FML_DCHECK(!image->isTextureBacked()); |
32 | |
33 | TRACE_EVENT0("flutter" , __FUNCTION__); |
34 | flow.Step(label: __FUNCTION__); |
35 | |
36 | if (resized_dimensions.isEmpty()) { |
37 | FML_LOG(ERROR) << "Could not resize to empty dimensions." ; |
38 | return nullptr; |
39 | } |
40 | |
41 | if (image->dimensions() == resized_dimensions) { |
42 | return image->makeRasterImage(); |
43 | } |
44 | |
45 | const auto scaled_image_info = |
46 | image->imageInfo().makeDimensions(newSize: resized_dimensions); |
47 | |
48 | SkBitmap scaled_bitmap; |
49 | if (!scaled_bitmap.tryAllocPixels(info: scaled_image_info)) { |
50 | FML_LOG(ERROR) << "Failed to allocate memory for bitmap of size " |
51 | << scaled_image_info.computeMinByteSize() << "B" ; |
52 | return nullptr; |
53 | } |
54 | |
55 | if (!image->scalePixels( |
56 | dst: scaled_bitmap.pixmap(), |
57 | SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone), |
58 | cachingHint: SkImage::kDisallow_CachingHint)) { |
59 | FML_LOG(ERROR) << "Could not scale pixels" ; |
60 | return nullptr; |
61 | } |
62 | |
63 | // Marking this as immutable makes the MakeFromBitmap call share the pixels |
64 | // instead of copying. |
65 | scaled_bitmap.setImmutable(); |
66 | |
67 | auto scaled_image = SkImages::RasterFromBitmap(bitmap: scaled_bitmap); |
68 | if (!scaled_image) { |
69 | FML_LOG(ERROR) << "Could not create a scaled image from a scaled bitmap." ; |
70 | return nullptr; |
71 | } |
72 | |
73 | return scaled_image; |
74 | } |
75 | |
76 | static sk_sp<SkImage> ImageFromDecompressedData( |
77 | ImageDescriptor* descriptor, |
78 | uint32_t target_width, |
79 | uint32_t target_height, |
80 | const fml::tracing::TraceFlow& flow) { |
81 | TRACE_EVENT0("flutter" , __FUNCTION__); |
82 | flow.Step(label: __FUNCTION__); |
83 | auto image = SkImages::RasterFromData( |
84 | info: descriptor->image_info(), pixels: descriptor->data(), rowBytes: descriptor->row_bytes()); |
85 | |
86 | if (!image) { |
87 | FML_LOG(ERROR) << "Could not create image from decompressed bytes." ; |
88 | return nullptr; |
89 | } |
90 | |
91 | if (!target_width && !target_height) { |
92 | // No resizing requested. Just rasterize the image. |
93 | return image->makeRasterImage(); |
94 | } |
95 | |
96 | return ResizeRasterImage(image, resized_dimensions: SkISize::Make(w: target_width, h: target_height), |
97 | flow); |
98 | } |
99 | |
100 | sk_sp<SkImage> ImageDecoderSkia::ImageFromCompressedData( |
101 | ImageDescriptor* descriptor, |
102 | uint32_t target_width, |
103 | uint32_t target_height, |
104 | const fml::tracing::TraceFlow& flow) { |
105 | TRACE_EVENT0("flutter" , __FUNCTION__); |
106 | flow.Step(label: __FUNCTION__); |
107 | |
108 | if (!descriptor->should_resize(target_width, target_height)) { |
109 | // No resizing requested. Just decode & rasterize the image. |
110 | sk_sp<SkImage> image = descriptor->image(); |
111 | return image ? image->makeRasterImage() : nullptr; |
112 | } |
113 | |
114 | const SkISize source_dimensions = descriptor->image_info().dimensions(); |
115 | const SkISize resized_dimensions = {.fWidth: static_cast<int32_t>(target_width), |
116 | .fHeight: static_cast<int32_t>(target_height)}; |
117 | |
118 | auto decode_dimensions = descriptor->get_scaled_dimensions( |
119 | scale: std::max(a: static_cast<float>(resized_dimensions.width()) / |
120 | source_dimensions.width(), |
121 | b: static_cast<float>(resized_dimensions.height()) / |
122 | source_dimensions.height())); |
123 | |
124 | // If the codec supports efficient sub-pixel decoding, decoded at a resolution |
125 | // close to the target resolution before resizing. |
126 | if (decode_dimensions != source_dimensions) { |
127 | auto scaled_image_info = |
128 | descriptor->image_info().makeDimensions(newSize: decode_dimensions); |
129 | |
130 | SkBitmap scaled_bitmap; |
131 | if (!scaled_bitmap.tryAllocPixels(info: scaled_image_info)) { |
132 | FML_LOG(ERROR) << "Failed to allocate memory for bitmap of size " |
133 | << scaled_image_info.computeMinByteSize() << "B" ; |
134 | return nullptr; |
135 | } |
136 | |
137 | const auto& pixmap = scaled_bitmap.pixmap(); |
138 | if (descriptor->get_pixels(pixmap)) { |
139 | // Marking this as immutable makes the MakeFromBitmap call share |
140 | // the pixels instead of copying. |
141 | scaled_bitmap.setImmutable(); |
142 | |
143 | auto decoded_image = SkImages::RasterFromBitmap(bitmap: scaled_bitmap); |
144 | FML_DCHECK(decoded_image); |
145 | if (!decoded_image) { |
146 | FML_LOG(ERROR) |
147 | << "Could not create a scaled image from a scaled bitmap." ; |
148 | return nullptr; |
149 | } |
150 | return ResizeRasterImage(image: decoded_image, resized_dimensions, flow); |
151 | } |
152 | } |
153 | |
154 | auto image = descriptor->image(); |
155 | if (!image) { |
156 | return nullptr; |
157 | } |
158 | |
159 | return ResizeRasterImage(image, resized_dimensions, flow); |
160 | } |
161 | |
162 | static SkiaGPUObject<SkImage> UploadRasterImage( |
163 | sk_sp<SkImage> image, |
164 | const fml::WeakPtr<IOManager>& io_manager, |
165 | const fml::tracing::TraceFlow& flow) { |
166 | TRACE_EVENT0("flutter" , __FUNCTION__); |
167 | flow.Step(label: __FUNCTION__); |
168 | |
169 | // Should not already be a texture image because that is the entire point of |
170 | // the this method. |
171 | FML_DCHECK(!image->isTextureBacked()); |
172 | |
173 | if (!io_manager->GetResourceContext() || !io_manager->GetSkiaUnrefQueue()) { |
174 | FML_LOG(ERROR) |
175 | << "Could not acquire context of release queue for texture upload." ; |
176 | return {}; |
177 | } |
178 | |
179 | SkPixmap pixmap; |
180 | if (!image->peekPixels(pixmap: &pixmap)) { |
181 | FML_LOG(ERROR) << "Could not peek pixels of image for texture upload." ; |
182 | return {}; |
183 | } |
184 | |
185 | SkiaGPUObject<SkImage> result; |
186 | io_manager->GetIsGpuDisabledSyncSwitch()->Execute( |
187 | handlers: fml::SyncSwitch::Handlers() |
188 | .SetIfTrue([&result, &pixmap, &image] { |
189 | SkSafeRef(obj: image.get()); |
190 | sk_sp<SkImage> texture_image = SkImages::RasterFromPixmap( |
191 | pixmap, |
192 | rasterReleaseProc: [](const void* pixels, SkImages::ReleaseContext context) { |
193 | SkSafeUnref(obj: static_cast<SkImage*>(context)); |
194 | }, |
195 | releaseContext: image.get()); |
196 | result = {std::move(texture_image), nullptr}; |
197 | }) |
198 | .SetIfFalse([&result, context = io_manager->GetResourceContext(), |
199 | &pixmap, queue = io_manager->GetSkiaUnrefQueue()] { |
200 | TRACE_EVENT0("flutter" , "MakeCrossContextImageFromPixmap" ); |
201 | sk_sp<SkImage> texture_image = |
202 | SkImages::CrossContextTextureFromPixmap( |
203 | context: context.get(), // context |
204 | pixmap, // pixmap |
205 | buildMips: true, // buildMips, |
206 | limitToMaxTextureSize: true // limitToMaxTextureSize |
207 | ); |
208 | if (!texture_image) { |
209 | FML_LOG(ERROR) << "Could not make x-context image." ; |
210 | result = {}; |
211 | } else { |
212 | result = {std::move(texture_image), queue}; |
213 | } |
214 | })); |
215 | |
216 | return result; |
217 | } |
218 | |
219 | // |ImageDecoder| |
220 | void ImageDecoderSkia::Decode(fml::RefPtr<ImageDescriptor> descriptor_ref_ptr, |
221 | uint32_t target_width, |
222 | uint32_t target_height, |
223 | const ImageResult& callback) { |
224 | TRACE_EVENT0("flutter" , __FUNCTION__); |
225 | fml::tracing::TraceFlow flow(__FUNCTION__); |
226 | |
227 | // ImageDescriptors have Dart peers that must be collected on the UI thread. |
228 | // However, closures in MakeCopyable below capture the descriptor. The |
229 | // captures of copyable closures may be collected on any of the thread |
230 | // participating in task execution. |
231 | // |
232 | // To avoid this issue, we resort to manually reference counting the |
233 | // descriptor. Since all task flows invoke the `result` callback, the raw |
234 | // descriptor is retained in the beginning and released in the `result` |
235 | // callback. |
236 | // |
237 | // `ImageDecoder::Decode` itself is invoked on the UI thread, so the |
238 | // collection of the smart pointer from which we obtained the raw descriptor |
239 | // is fine in this scope. |
240 | auto raw_descriptor = descriptor_ref_ptr.get(); |
241 | raw_descriptor->AddRef(); |
242 | |
243 | FML_DCHECK(callback); |
244 | FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
245 | |
246 | // Always service the callback (and cleanup the descriptor) on the UI thread. |
247 | auto result = |
248 | [callback, raw_descriptor, ui_runner = runners_.GetUITaskRunner()]( |
249 | SkiaGPUObject<SkImage> image, fml::tracing::TraceFlow flow) { |
250 | ui_runner->PostTask(task: fml::MakeCopyable( |
251 | lambda: [callback, raw_descriptor, image = std::move(image), |
252 | flow = std::move(flow)]() mutable { |
253 | // We are going to terminate the trace flow here. Flows cannot |
254 | // terminate without a base trace. Add one explicitly. |
255 | TRACE_EVENT0("flutter" , "ImageDecodeCallback" ); |
256 | flow.End(); |
257 | callback(DlImageGPU::Make(image: std::move(image)), {}); |
258 | raw_descriptor->Release(); |
259 | })); |
260 | }; |
261 | |
262 | if (!raw_descriptor->data() || raw_descriptor->data()->size() == 0) { |
263 | result({}, std::move(flow)); |
264 | return; |
265 | } |
266 | |
267 | concurrent_task_runner_->PostTask( |
268 | task: fml::MakeCopyable(lambda: [raw_descriptor, // |
269 | io_manager = io_manager_, // |
270 | io_runner = runners_.GetIOTaskRunner(), // |
271 | result, // |
272 | target_width = target_width, // |
273 | target_height = target_height, // |
274 | flow = std::move(flow) // |
275 | ]() mutable { |
276 | // Step 1: Decompress the image. |
277 | // On Worker. |
278 | |
279 | auto decompressed = raw_descriptor->is_compressed() |
280 | ? ImageFromCompressedData(descriptor: raw_descriptor, // |
281 | target_width, // |
282 | target_height, // |
283 | flow) |
284 | : ImageFromDecompressedData(descriptor: raw_descriptor, // |
285 | target_width, // |
286 | target_height, // |
287 | flow); |
288 | |
289 | if (!decompressed) { |
290 | FML_DLOG(ERROR) << "Could not decompress image." ; |
291 | result({}, std::move(flow)); |
292 | return; |
293 | } |
294 | |
295 | // Step 2: Update the image to the GPU. |
296 | // On IO Thread. |
297 | |
298 | io_runner->PostTask(task: fml::MakeCopyable(lambda: [io_manager, decompressed, result, |
299 | flow = |
300 | std::move(flow)]() mutable { |
301 | if (!io_manager) { |
302 | FML_DLOG(ERROR) << "Could not acquire IO manager." ; |
303 | result({}, std::move(flow)); |
304 | return; |
305 | } |
306 | |
307 | // If the IO manager does not have a resource context, the caller |
308 | // might not have set one or a software backend could be in use. |
309 | // Either way, just return the image as-is. |
310 | if (!io_manager->GetResourceContext()) { |
311 | result({std::move(decompressed), io_manager->GetSkiaUnrefQueue()}, |
312 | std::move(flow)); |
313 | return; |
314 | } |
315 | |
316 | auto uploaded = |
317 | UploadRasterImage(image: std::move(decompressed), io_manager, flow); |
318 | |
319 | if (!uploaded.skia_object()) { |
320 | FML_DLOG(ERROR) << "Could not upload image to the GPU." ; |
321 | result({}, std::move(flow)); |
322 | return; |
323 | } |
324 | |
325 | // Finally, all done. |
326 | result(std::move(uploaded), std::move(flow)); |
327 | })); |
328 | })); |
329 | } |
330 | |
331 | } // namespace flutter |
332 | |