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