1 /*
2 * Copyright 2019 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "tools/gpu/YUVUtils.h"
9
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColorFilter.h"
13 #include "include/core/SkColorPriv.h"
14 #include "include/core/SkData.h"
15 #include "include/core/SkSurface.h"
16 #include "include/gpu/ganesh/GrRecordingContext.h"
17 #include "include/gpu/ganesh/GrYUVABackendTextures.h"
18 #include "include/gpu/ganesh/SkImageGanesh.h"
19 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
20 #include "src/codec/SkCodecImageGenerator.h"
21 #include "src/core/SkYUVAInfoLocation.h"
22 #include "src/core/SkYUVMath.h"
23 #include "src/gpu/ganesh/GrDirectContextPriv.h"
24 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
25 #include "src/image/SkImage_Base.h"
26 #include "tools/gpu/ManagedBackendTexture.h"
27
28 #ifdef SK_GRAPHITE
29 #include "include/gpu/graphite/Image.h"
30 #include "include/gpu/graphite/YUVABackendTextures.h"
31 #include "include/private/base/SkTArray.h"
32 #include "src/core/SkAutoPixmapStorage.h"
33 #endif
34
35 namespace {
36
convert_yuva_to_rgba(const float mtx[20],uint8_t yuva[4])37 static SkPMColor convert_yuva_to_rgba(const float mtx[20], uint8_t yuva[4]) {
38 uint8_t y = yuva[0];
39 uint8_t u = yuva[1];
40 uint8_t v = yuva[2];
41 uint8_t a = yuva[3];
42
43 uint8_t r = SkTPin(SkScalarRoundToInt(mtx[ 0]*y + mtx[ 1]*u + mtx[ 2]*v + mtx[ 4]*255), 0, 255);
44 uint8_t g = SkTPin(SkScalarRoundToInt(mtx[ 5]*y + mtx[ 6]*u + mtx[ 7]*v + mtx[ 9]*255), 0, 255);
45 uint8_t b = SkTPin(SkScalarRoundToInt(mtx[10]*y + mtx[11]*u + mtx[12]*v + mtx[14]*255), 0, 255);
46
47 return SkPremultiplyARGBInline(a, r, g, b);
48 }
49
look_up(SkPoint normPt,const SkPixmap & pmap,SkColorChannel channel)50 static uint8_t look_up(SkPoint normPt, const SkPixmap& pmap, SkColorChannel channel) {
51 SkASSERT(normPt.x() > 0 && normPt.x() < 1.0f);
52 SkASSERT(normPt.y() > 0 && normPt.y() < 1.0f);
53 int x = SkScalarFloorToInt(normPt.x() * pmap.width());
54 int y = SkScalarFloorToInt(normPt.y() * pmap.height());
55
56 auto ii = pmap.info().makeColorType(kRGBA_8888_SkColorType).makeWH(1, 1);
57 uint32_t pixel;
58 SkAssertResult(pmap.readPixels(ii, &pixel, sizeof(pixel), x, y));
59 int shift = static_cast<int>(channel) * 8;
60 return static_cast<uint8_t>((pixel >> shift) & 0xff);
61 }
62
63 class Generator : public SkImageGenerator {
64 public:
Generator(SkYUVAPixmaps pixmaps,sk_sp<SkColorSpace> cs)65 Generator(SkYUVAPixmaps pixmaps, sk_sp<SkColorSpace> cs)
66 : SkImageGenerator(SkImageInfo::Make(pixmaps.yuvaInfo().dimensions(),
67 kN32_SkColorType,
68 kPremul_SkAlphaType,
69 std::move(cs)))
70 , fPixmaps(std::move(pixmaps)) {}
71
72 protected:
onGetPixels(const SkImageInfo & info,void * pixels,size_t rowBytes,const Options &)73 bool onGetPixels(const SkImageInfo& info,
74 void* pixels,
75 size_t rowBytes,
76 const Options&) override {
77 if (kUnknown_SkColorType == fFlattened.colorType()) {
78 fFlattened.allocPixels(info);
79 SkASSERT(info == this->getInfo());
80
81 float mtx[20];
82 SkColorMatrix_YUV2RGB(fPixmaps.yuvaInfo().yuvColorSpace(), mtx);
83 SkYUVAInfo::YUVALocations yuvaLocations = fPixmaps.toYUVALocations();
84 SkASSERT(SkYUVAInfo::YUVALocation::AreValidLocations(yuvaLocations));
85
86 SkMatrix om = fPixmaps.yuvaInfo().originMatrix();
87 SkAssertResult(om.invert(&om));
88 float normX = 1.f/info.width();
89 float normY = 1.f/info.height();
90 if (SkEncodedOriginSwapsWidthHeight(fPixmaps.yuvaInfo().origin())) {
91 using std::swap;
92 swap(normX, normY);
93 }
94 for (int y = 0; y < info.height(); ++y) {
95 for (int x = 0; x < info.width(); ++x) {
96 SkPoint xy1 {(x + 0.5f),
97 (y + 0.5f)};
98 xy1 = om.mapPoint(xy1);
99 xy1.fX *= normX;
100 xy1.fY *= normY;
101
102 uint8_t yuva[4] = {0, 0, 0, 255};
103
104 for (auto c : {SkYUVAInfo::YUVAChannels::kY,
105 SkYUVAInfo::YUVAChannels::kU,
106 SkYUVAInfo::YUVAChannels::kV}) {
107 const auto& pmap = fPixmaps.plane(yuvaLocations[c].fPlane);
108 yuva[c] = look_up(xy1, pmap, yuvaLocations[c].fChannel);
109 }
110 auto [aPlane, aChan] = yuvaLocations[SkYUVAInfo::YUVAChannels::kA];
111 if (aPlane >= 0) {
112 const auto& pmap = fPixmaps.plane(aPlane);
113 yuva[3] = look_up(xy1, pmap, aChan);
114 }
115
116 // Making premul here.
117 *fFlattened.getAddr32(x, y) = convert_yuva_to_rgba(mtx, yuva);
118 }
119 }
120 }
121
122 return fFlattened.readPixels(info, pixels, rowBytes, 0, 0);
123 }
124
onQueryYUVAInfo(const SkYUVAPixmapInfo::SupportedDataTypes & types,SkYUVAPixmapInfo * info) const125 bool onQueryYUVAInfo(const SkYUVAPixmapInfo::SupportedDataTypes& types,
126 SkYUVAPixmapInfo* info) const override {
127 *info = fPixmaps.pixmapsInfo();
128 return info->isValid();
129 }
130
onGetYUVAPlanes(const SkYUVAPixmaps & pixmaps)131 bool onGetYUVAPlanes(const SkYUVAPixmaps& pixmaps) override {
132 SkASSERT(pixmaps.yuvaInfo() == fPixmaps.yuvaInfo());
133 for (int i = 0; i < pixmaps.numPlanes(); ++i) {
134 SkASSERT(fPixmaps.plane(i).colorType() == pixmaps.plane(i).colorType());
135 SkASSERT(fPixmaps.plane(i).dimensions() == pixmaps.plane(i).dimensions());
136 SkASSERT(fPixmaps.plane(i).rowBytes() == pixmaps.plane(i).rowBytes());
137 fPixmaps.plane(i).readPixels(pixmaps.plane(i));
138 }
139 return true;
140 }
141
142 private:
143 SkYUVAPixmaps fPixmaps;
144 SkBitmap fFlattened;
145 };
146
147 } // anonymous namespace
148
149 namespace sk_gpu_test {
150
151 std::tuple<std::array<sk_sp<SkImage>, SkYUVAInfo::kMaxPlanes>, SkYUVAInfo>
MakeYUVAPlanesAsA8(SkImage * src,SkYUVColorSpace cs,SkYUVAInfo::Subsampling ss,GrRecordingContext * rContext)152 MakeYUVAPlanesAsA8(SkImage* src,
153 SkYUVColorSpace cs,
154 SkYUVAInfo::Subsampling ss,
155 GrRecordingContext* rContext) {
156 float rgbToYUV[20];
157 SkColorMatrix_RGB2YUV(cs, rgbToYUV);
158
159 SkYUVAInfo::PlaneConfig config = src->isOpaque() ? SkYUVAInfo::PlaneConfig::kY_U_V
160 : SkYUVAInfo::PlaneConfig::kY_U_V_A;
161 SkISize dims[SkYUVAInfo::kMaxPlanes];
162 int n = SkYUVAInfo::PlaneDimensions(src->dimensions(),
163 config,
164 ss,
165 kTopLeft_SkEncodedOrigin,
166 dims);
167 std::array<sk_sp<SkImage>, 4> planes;
168 for (int i = 0; i < n; ++i) {
169 SkImageInfo info = SkImageInfo::MakeA8(dims[i]);
170 sk_sp<SkSurface> surf;
171 if (rContext) {
172 surf = SkSurfaces::RenderTarget(rContext, skgpu::Budgeted::kYes, info, 1, nullptr);
173 } else {
174 surf = SkSurfaces::Raster(info);
175 }
176 if (!surf) {
177 return {};
178 }
179
180 SkPaint paint;
181 paint.setBlendMode(SkBlendMode::kSrc);
182
183 // Make a matrix with the ith row of rgbToYUV copied to the A row since we're drawing to A8.
184 float m[20] = {};
185 std::copy_n(rgbToYUV + 5*i, 5, m + 15);
186 paint.setColorFilter(SkColorFilters::Matrix(m));
187 surf->getCanvas()->drawImageRect(src,
188 SkRect::Make(dims[i]),
189 SkSamplingOptions(SkFilterMode::kLinear),
190 &paint);
191 planes[i] = surf->makeImageSnapshot();
192 if (!planes[i]) {
193 return {};
194 }
195 }
196 SkYUVAInfo info(src->dimensions(), config, ss, cs);
197 return {planes, info};
198 }
199
Make(sk_sp<SkData> data,skgpu::Mipmapped mipmapped,sk_sp<SkColorSpace> cs)200 std::unique_ptr<LazyYUVImage> LazyYUVImage::Make(sk_sp<SkData> data,
201 skgpu::Mipmapped mipmapped,
202 sk_sp<SkColorSpace> cs) {
203 std::unique_ptr<LazyYUVImage> image(new LazyYUVImage());
204 if (image->reset(std::move(data), mipmapped, std::move(cs))) {
205 return image;
206 } else {
207 return nullptr;
208 }
209 }
210
Make(SkYUVAPixmaps pixmaps,skgpu::Mipmapped mipmapped,sk_sp<SkColorSpace> cs)211 std::unique_ptr<LazyYUVImage> LazyYUVImage::Make(SkYUVAPixmaps pixmaps,
212 skgpu::Mipmapped mipmapped,
213 sk_sp<SkColorSpace> cs) {
214 std::unique_ptr<LazyYUVImage> image(new LazyYUVImage());
215 if (image->reset(std::move(pixmaps), mipmapped, std::move(cs))) {
216 return image;
217 } else {
218 return nullptr;
219 }
220 }
221
refImage(GrRecordingContext * rContext,Type type)222 sk_sp<SkImage> LazyYUVImage::refImage(GrRecordingContext* rContext, Type type) {
223 if (this->ensureYUVImage(rContext, type)) {
224 size_t idx = static_cast<size_t>(type);
225 SkASSERT(idx < std::size(fYUVImage));
226 return fYUVImage[idx];
227 } else {
228 return nullptr;
229 }
230 }
231
232 #if defined(SK_GRAPHITE)
refImage(skgpu::graphite::Recorder * recorder,Type type)233 sk_sp<SkImage> LazyYUVImage::refImage(skgpu::graphite::Recorder* recorder, Type type) {
234 if (this->ensureYUVImage(recorder, type)) {
235 size_t idx = static_cast<size_t>(type);
236 SkASSERT(idx < std::size(fYUVImage));
237 return fYUVImage[idx];
238 } else {
239 return nullptr;
240 }
241 }
242 #endif
243
reset(sk_sp<SkData> data,skgpu::Mipmapped mipmapped,sk_sp<SkColorSpace> cs)244 bool LazyYUVImage::reset(sk_sp<SkData> data, skgpu::Mipmapped mipmapped, sk_sp<SkColorSpace> cs) {
245 fMipmapped = mipmapped;
246 auto codec = SkCodecImageGenerator::MakeFromEncodedCodec(data);
247 if (!codec) {
248 return false;
249 }
250
251 SkYUVAPixmapInfo yuvaPixmapInfo;
252 if (!codec->queryYUVAInfo(SkYUVAPixmapInfo::SupportedDataTypes::All(), &yuvaPixmapInfo)) {
253 return false;
254 }
255 fPixmaps = SkYUVAPixmaps::Allocate(yuvaPixmapInfo);
256 if (!fPixmaps.isValid()) {
257 return false;
258 }
259
260 if (!codec->getYUVAPlanes(fPixmaps)) {
261 return false;
262 }
263
264 fColorSpace = std::move(cs);
265
266 // The SkPixmap data is fully configured now for MakeFromYUVAPixmaps once we get a GrContext
267 return true;
268 }
269
reset(SkYUVAPixmaps pixmaps,skgpu::Mipmapped mipmapped,sk_sp<SkColorSpace> cs)270 bool LazyYUVImage::reset(SkYUVAPixmaps pixmaps,
271 skgpu::Mipmapped mipmapped,
272 sk_sp<SkColorSpace> cs) {
273 if (!pixmaps.isValid()) {
274 return false;
275 }
276 fMipmapped = mipmapped;
277 if (pixmaps.ownsStorage()) {
278 fPixmaps = std::move(pixmaps);
279 } else {
280 fPixmaps = SkYUVAPixmaps::MakeCopy(std::move(pixmaps));
281 }
282 fColorSpace = std::move(cs);
283 // The SkPixmap data is fully configured now for MakeFromYUVAPixmaps once we get a GrContext
284 return true;
285 }
286
ensureYUVImage(GrRecordingContext * rContext,Type type)287 bool LazyYUVImage::ensureYUVImage(GrRecordingContext* rContext, Type type) {
288 size_t idx = static_cast<size_t>(type);
289 SkASSERT(idx < std::size(fYUVImage));
290 if (fYUVImage[idx] && fYUVImage[idx]->isValid(rContext)) {
291 return true; // Have already made a YUV image valid for this context.
292 }
293 // Try to make a new YUV image for this context.
294 switch (type) {
295 case Type::kFromPixmaps:
296 if (!rContext || rContext->abandoned()) {
297 return false;
298 }
299 fYUVImage[idx] = SkImages::TextureFromYUVAPixmaps(rContext,
300 fPixmaps,
301 fMipmapped,
302 /*limit to max tex size*/ false,
303 fColorSpace);
304 break;
305 case Type::kFromGenerator: {
306 // Make sure the generator has ownership of its backing planes.
307 auto generator = std::make_unique<Generator>(fPixmaps, fColorSpace);
308 fYUVImage[idx] = SkImages::DeferredFromGenerator(std::move(generator));
309 break;
310 }
311 case Type::kFromTextures:
312 if (!rContext || rContext->abandoned()) {
313 return false;
314 }
315 if (auto direct = rContext->asDirectContext()) {
316 sk_sp<sk_gpu_test::ManagedBackendTexture> mbets[SkYUVAInfo::kMaxPlanes];
317 GrBackendTexture textures[SkYUVAInfo::kMaxPlanes];
318 for (int i = 0; i < fPixmaps.numPlanes(); ++i) {
319 mbets[i] = sk_gpu_test::ManagedBackendTexture::MakeFromPixmap(
320 direct,
321 fPixmaps.plane(i),
322 fMipmapped,
323 skgpu::Renderable::kNo,
324 skgpu::Protected::kNo);
325 if (mbets[i]) {
326 textures[i] = mbets[i]->texture();
327 } else {
328 return false;
329 }
330 }
331 GrYUVABackendTextures yuvaTextures(fPixmaps.yuvaInfo(),
332 textures,
333 kTopLeft_GrSurfaceOrigin);
334 if (!yuvaTextures.isValid()) {
335 return false;
336 }
337 void* planeRelContext =
338 sk_gpu_test::ManagedBackendTexture::MakeYUVAReleaseContext(mbets);
339 fYUVImage[idx] = SkImages::TextureFromYUVATextures(
340 direct,
341 yuvaTextures,
342 fColorSpace,
343 sk_gpu_test::ManagedBackendTexture::ReleaseProc,
344 planeRelContext);
345 }
346 break;
347 case Type::kFromImages:
348 // Not supported in Ganesh
349 return false;
350 }
351 return fYUVImage[idx] != nullptr;
352 }
353
354 #if defined(SK_GRAPHITE)
355 using BackendTexture = skgpu::graphite::BackendTexture;
356 using Recorder = skgpu::graphite::Recorder;
357 using YUVABackendTextures = skgpu::graphite::YUVABackendTextures;
358
ensureYUVImage(Recorder * recorder,Type type)359 bool LazyYUVImage::ensureYUVImage(Recorder* recorder, Type type) {
360 size_t idx = static_cast<size_t>(type);
361 SkASSERT(idx < std::size(fYUVImage));
362 if (fYUVImage[idx] && as_IB(fYUVImage[idx])->isGraphiteBacked()) {
363 return true; // Have already made a YUV image suitable for Graphite.
364 }
365 // Try to make a new Graphite YUV image
366 switch (type) {
367 case Type::kFromPixmaps:
368 if (!recorder) {
369 return false;
370 }
371 fYUVImage[idx] =
372 SkImages::TextureFromYUVAPixmaps(recorder,
373 fPixmaps,
374 {fMipmapped == skgpu::Mipmapped::kYes},
375 /*limitToMaxTextureSize=*/false,
376 fColorSpace);
377 break;
378 case Type::kFromGenerator: {
379 // Make sure the generator has ownership of its backing planes.
380 auto generator = std::make_unique<Generator>(fPixmaps, fColorSpace);
381 fYUVImage[idx] = SkImages::DeferredFromGenerator(std::move(generator));
382 break;
383 }
384 case Type::kFromTextures: {
385 if (!recorder) {
386 return false;
387 }
388
389 sk_sp<sk_gpu_test::ManagedGraphiteTexture> mbets[SkYUVAInfo::kMaxPlanes];
390 BackendTexture textures[SkYUVAInfo::kMaxPlanes];
391 for (int i = 0; i < fPixmaps.numPlanes(); ++i) {
392 // MakeFromPixmap will handle generating the upper mipmap levels if necessary.
393 mbets[i] = sk_gpu_test::ManagedGraphiteTexture::MakeFromPixmap(
394 recorder,
395 fPixmaps.plane(i),
396 fMipmapped,
397 skgpu::Renderable::kNo,
398 skgpu::Protected::kNo);
399 if (mbets[i]) {
400 textures[i] = mbets[i]->texture();
401 } else {
402 return false;
403 }
404 }
405 YUVABackendTextures yuvaTextures(recorder,
406 fPixmaps.yuvaInfo(),
407 textures);
408 if (!yuvaTextures.isValid()) {
409 return false;
410 }
411 void* imageRelContext =
412 sk_gpu_test::ManagedGraphiteTexture::MakeYUVAReleaseContext(mbets);
413 fYUVImage[idx] = SkImages::TextureFromYUVATextures(
414 recorder,
415 yuvaTextures,
416 fColorSpace,
417 sk_gpu_test::ManagedGraphiteTexture::ImageReleaseProc,
418 imageRelContext);
419 break;
420 }
421 case Type::kFromImages: {
422 if (!recorder) {
423 return false;
424 }
425
426 sk_sp<SkImage> planeImgs[SkYUVAInfo::kMaxPlanes];
427
428 using SkImages::GenerateMipmapsFromBase;
429 GenerateMipmapsFromBase genMipmaps = GenerateMipmapsFromBase::kNo;
430 if (fMipmapped == skgpu::Mipmapped::kYes) {
431 genMipmaps = GenerateMipmapsFromBase::kYes;
432 }
433
434 for (int i = 0; i < fPixmaps.numPlanes(); ++i) {
435 const auto& plane = fPixmaps.plane(i);
436 sk_sp<ManagedGraphiteTexture> mbet;
437 if (fMipmapped == skgpu::Mipmapped::kYes) {
438 mbet = ManagedGraphiteTexture::MakeUnInit(recorder,
439 plane.info(),
440 skgpu::Mipmapped::kYes,
441 skgpu::Renderable::kNo);
442 // We allocate a full mip set because updateBackendTexture requires it. However,
443 // the non-base levels are cleared to red. We rely on SkImages::WrapTexture
444 // to actually generate the contents from the base level for each plane on the
445 // GPU. This exercises the case where the client wants a mipmapped YUV image but
446 // only provides the base level contents.
447 int levelCnt = SkMipmap::ComputeLevelCount(plane.dimensions());
448 skia_private::TArray<SkAutoPixmapStorage> levelStorage(levelCnt);
449 skia_private::TArray<SkPixmap> levels(levelCnt + 1);
450 levels.push_back(plane);
451 for (int l = 0; l < levelCnt; ++l) {
452 SkISize dims = SkMipmap::ComputeLevelSize(plane.dimensions(), l);
453 SkAutoPixmapStorage level;
454 level.alloc(plane.info().makeDimensions(dims));
455 level.erase(SK_ColorRED);
456 levels.push_back(level);
457 levelStorage.push_back(std::move(level));
458 }
459 if (!mbet || !recorder->updateBackendTexture(mbet->texture(),
460 levels.data(),
461 levels.size())) {
462 return false;
463 }
464 } else {
465 mbet = ManagedGraphiteTexture::MakeFromPixmap(recorder,
466 plane,
467 skgpu::Mipmapped::kNo,
468 skgpu::Renderable::kNo);
469 if (!mbet) {
470 return false;
471 }
472 }
473 planeImgs[i] = SkImages::WrapTexture(recorder,
474 mbet->texture(),
475 plane.colorType(),
476 plane.alphaType(),
477 fColorSpace,
478 skgpu::Origin::kTopLeft,
479 genMipmaps,
480 ManagedGraphiteTexture::ImageReleaseProc,
481 mbet->releaseContext());
482 }
483
484 fYUVImage[idx] = SkImages::TextureFromYUVAImages(
485 recorder,
486 fPixmaps.yuvaInfo(),
487 planeImgs,
488 fColorSpace);
489 break;
490 }
491 }
492 return fYUVImage[idx] != nullptr;
493 }
494 #endif
495
496 } // namespace sk_gpu_test
497