xref: /aosp_15_r20/external/skia/src/gpu/graphite/Image_YUVA_Graphite.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2023 Google LLC
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 "src/gpu/graphite/Image_YUVA_Graphite.h"
9 
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColorSpace.h"
13 #include "include/core/SkImage.h"
14 #include "include/core/SkSurface.h"
15 #include "include/gpu/GpuTypes.h"
16 #include "include/gpu/graphite/Image.h"
17 #include "include/gpu/graphite/Recorder.h"
18 #include "include/gpu/graphite/Surface.h"
19 #include "src/core/SkYUVAInfoLocation.h"
20 #include "src/gpu/graphite/Caps.h"
21 #include "src/gpu/graphite/Image_Graphite.h"
22 #include "src/gpu/graphite/Log.h"
23 #include "src/gpu/graphite/RecorderPriv.h"
24 #include "src/gpu/graphite/ResourceProvider.h"
25 #include "src/gpu/graphite/Texture.h"
26 #include "src/gpu/graphite/TextureProxy.h"
27 #include "src/gpu/graphite/TextureProxyView.h"
28 #include "src/gpu/graphite/TextureUtils.h"
29 #include "src/shaders/SkImageShader.h"
30 
31 
32 namespace skgpu::graphite {
33 
34 namespace {
35 
36 constexpr auto kAssumedColorType = kRGBA_8888_SkColorType;
37 
38 static constexpr int kY = static_cast<int>(SkYUVAInfo::kY);
39 static constexpr int kU = static_cast<int>(SkYUVAInfo::kU);
40 static constexpr int kV = static_cast<int>(SkYUVAInfo::kV);
41 static constexpr int kA = static_cast<int>(SkYUVAInfo::kA);
42 
yuva_alpha_type(const SkYUVAInfo & yuvaInfo)43 static SkAlphaType yuva_alpha_type(const SkYUVAInfo& yuvaInfo) {
44     // If an alpha channel is present we always use kPremul. This is because, although the planar
45     // data is always un-premul and the final interleaved RGBA sample produced in the shader is
46     // unpremul (and similar if flattened), the client is expecting premul.
47     return yuvaInfo.hasAlpha() ? kPremul_SkAlphaType : kOpaque_SkAlphaType;
48 }
49 
50 } // anonymous
51 
Image_YUVA(const YUVAProxies & proxies,const SkYUVAInfo & yuvaInfo,sk_sp<SkColorSpace> imageColorSpace)52 Image_YUVA::Image_YUVA(const YUVAProxies& proxies,
53                        const SkYUVAInfo& yuvaInfo,
54                        sk_sp<SkColorSpace> imageColorSpace)
55         : Image_Base(SkImageInfo::Make(yuvaInfo.dimensions(),
56                                        kAssumedColorType,
57                                        yuva_alpha_type(yuvaInfo),
58                                        std::move(imageColorSpace)),
59                      kNeedNewImageUniqueID)
60         , fProxies(std::move(proxies))
61         , fYUVAInfo(yuvaInfo)
62         , fUVSubsampleFactors(SkYUVAInfo::SubsamplingFactors(yuvaInfo.subsampling())) {
63     // The caller should have checked this, just verifying.
64     SkASSERT(fYUVAInfo.isValid());
65     for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) {
66         if (!fProxies[i]) {
67             SkASSERT(i == kA);
68             continue;
69         }
70         if (fProxies[i].proxy()->mipmapped() == Mipmapped::kNo) {
71             fMipmapped = Mipmapped::kNo;
72         }
73         if (fProxies[i].proxy()->isProtected()) {
74             fProtected = Protected::kYes;
75         }
76     }
77 }
78 
79 Image_YUVA::~Image_YUVA() = default;
80 
Make(const Caps * caps,const SkYUVAInfo & yuvaInfo,SkSpan<TextureProxyView> planes,sk_sp<SkColorSpace> imageColorSpace)81 sk_sp<Image_YUVA> Image_YUVA::Make(const Caps* caps,
82                                    const SkYUVAInfo& yuvaInfo,
83                                    SkSpan<TextureProxyView> planes,
84                                    sk_sp<SkColorSpace> imageColorSpace) {
85     if (!yuvaInfo.isValid()) {
86         return nullptr;
87     }
88     SkImageInfo info = SkImageInfo::Make(
89             yuvaInfo.dimensions(), kAssumedColorType, yuva_alpha_type(yuvaInfo), imageColorSpace);
90     if (!SkImageInfoIsValid(info)) {
91         return nullptr;
92     }
93 
94     // Invoke the PlaneProxyFactoryFn for each plane and validate it against the plane config
95     const int numPlanes = yuvaInfo.numPlanes();
96     SkISize planeDimensions[SkYUVAInfo::kMaxPlanes];
97     if (numPlanes != yuvaInfo.planeDimensions(planeDimensions)) {
98         return nullptr;
99     }
100     uint32_t pixmapChannelmasks[SkYUVAInfo::kMaxPlanes];
101     for (int i = 0; i < numPlanes; ++i) {
102         if (!planes[i] || !caps->isTexturable(planes[i].proxy()->textureInfo())) {
103             return nullptr;
104         }
105         if (planes[i].dimensions() != planeDimensions[i]) {
106             return nullptr;
107         }
108         pixmapChannelmasks[i] = caps->channelMask(planes[i].proxy()->textureInfo());
109     }
110 
111     // Re-arrange the proxies from planes to channels
112     SkYUVAInfo::YUVALocations locations = yuvaInfo.toYUVALocations(pixmapChannelmasks);
113     int expectedPlanes;
114     if (!SkYUVAInfo::YUVALocation::AreValidLocations(locations, &expectedPlanes) ||
115         expectedPlanes != numPlanes) {
116         return nullptr;
117     }
118     // Y channel should match the YUVAInfo dimensions
119     if (planes[locations[kY].fPlane].dimensions() != yuvaInfo.dimensions()) {
120         return nullptr;
121     }
122     // UV channels should have planes with the same dimensions and subsampling factor.
123     if (planes[locations[kU].fPlane].dimensions() != planes[locations[kV].fPlane].dimensions()) {
124         return nullptr;
125     }
126     // If A channel is present, it should match the Y channel
127     if (locations[kA].fPlane >= 0 &&
128         planes[locations[kA].fPlane].dimensions() != yuvaInfo.dimensions()) {
129         return nullptr;
130     }
131 
132     if (yuvaInfo.planeSubsamplingFactors(locations[kU].fPlane) !=
133         yuvaInfo.planeSubsamplingFactors(locations[kV].fPlane)) {
134         return nullptr;
135     }
136 
137     // Re-arrange into YUVA channel order and apply the location to the swizzle
138     YUVAProxies channelProxies;
139     for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) {
140         auto [plane, channel] = locations[i];
141         if (plane >= 0) {
142             // Compose the YUVA location with the data swizzle. replaceSwizzle() is used since
143             // selectChannelInR() effectively does the composition (vs. Swizzle::Concat).
144             Swizzle channelSwizzle = planes[plane].swizzle().selectChannelInR((int) channel);
145             channelProxies[i] = planes[plane].replaceSwizzle(channelSwizzle);
146         } else if (i == kA) {
147             // The alpha channel is allowed to be not provided, set it to an empty view
148             channelProxies[i] = {};
149         } else {
150             SKGPU_LOG_W("YUVA channel %d does not have a valid location", i);
151             return nullptr;
152         }
153     }
154 
155     return sk_sp<Image_YUVA>(new Image_YUVA(std::move(channelProxies),
156                                             yuvaInfo,
157                                             std::move(imageColorSpace)));
158 }
159 
WrapImages(const Caps * caps,const SkYUVAInfo & yuvaInfo,SkSpan<const sk_sp<SkImage>> images,sk_sp<SkColorSpace> imageColorSpace)160 sk_sp<Image_YUVA> Image_YUVA::WrapImages(const Caps* caps,
161                                          const SkYUVAInfo& yuvaInfo,
162                                          SkSpan<const sk_sp<SkImage>> images,
163                                          sk_sp<SkColorSpace> imageColorSpace) {
164     if (SkTo<int>(images.size()) < yuvaInfo.numPlanes()) {
165         return nullptr;
166     }
167 
168     TextureProxyView planes[SkYUVAInfo::kMaxPlanes];
169     for (int i = 0; i < yuvaInfo.numPlanes(); ++i) {
170         planes[i] = AsView(images[i]);
171         if (!planes[i]) {
172             // A null image, or not graphite-backed, or not backed by a single texture.
173             return nullptr;
174         }
175         // The YUVA shader expects to sample from the red channel for single-channel textures, so
176         // reset the swizzle for alpha-only textures to compensate for that
177         if (images[i]->isAlphaOnly()) {
178             planes[i] = planes[i].makeSwizzle(Swizzle("aaaa"));
179         }
180     }
181 
182     sk_sp<Image_YUVA> image = Make(caps, yuvaInfo, SkSpan(planes), std::move(imageColorSpace));
183     if (image) {
184         // Unlike the other factories, this YUVA image shares the texture proxies with each plane
185         // Image, so if those are linked to Devices, it must inherit those same links.
186         for (int plane = 0; plane < yuvaInfo.numPlanes(); ++plane) {
187             SkASSERT(as_IB(images[plane])->isGraphiteBacked());
188             image->linkDevices(static_cast<Image_Base*>(images[plane].get()));
189         }
190     }
191     return image;
192 }
193 
textureSize() const194 size_t Image_YUVA::textureSize() const {
195     // We could look at the plane config and plane count to determine how many different textures
196     // to expect, but it's theoretically possible for an Image_YUVA to be constructed where the
197     // same TextureProxy is aliased to both the U and the V planes (and similarly for the Y and A)
198     // even when the plane config specifies that those channels are not packed into the same texture
199     //
200     // Given that it's simpler to just sum the total gpu memory of non-duplicate textures.
201     size_t size = 0;
202     for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) {
203         if (!fProxies[i]) {
204             continue; // Null channels (A) have no size.
205         }
206         bool repeat = false;
207         for (int j = i - 1; j >= 0; --j) {
208             if (fProxies[i].proxy() == fProxies[j].proxy()) {
209                 repeat = true;
210                 break;
211             }
212         }
213         if (!repeat) {
214             if (fProxies[i].proxy()->isInstantiated()) {
215                 size += fProxies[i].proxy()->texture()->gpuMemorySize();
216             } else {
217                 size += fProxies[i].proxy()->uninstantiatedGpuMemorySize();
218             }
219         }
220     }
221 
222     return size;
223 }
224 
onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const225 sk_sp<SkImage> Image_YUVA::onReinterpretColorSpace(sk_sp<SkColorSpace> newCS) const {
226     sk_sp<Image_YUVA> view{new Image_YUVA(fProxies,
227                                           fYUVAInfo,
228                                           std::move(newCS))};
229     // The new Image object shares the same texture planes, so it should also share linked Devices
230     view->linkDevices(this);
231     return view;
232 }
233 
234 }  // namespace skgpu::graphite
235