xref: /aosp_15_r20/external/skia/tools/gpu/YUVUtils.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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