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