/src/skia/src/gpu/ganesh/effects/GrYUVtoRGBEffect.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2018 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 "src/gpu/ganesh/effects/GrYUVtoRGBEffect.h" |
9 | | |
10 | | #include "include/core/SkAlphaType.h" |
11 | | #include "include/core/SkImageInfo.h" |
12 | | #include "include/core/SkRect.h" |
13 | | #include "include/core/SkSamplingOptions.h" |
14 | | #include "include/core/SkYUVAInfo.h" |
15 | | #include "include/private/SkSLSampleUsage.h" |
16 | | #include "include/private/base/SkAssert.h" |
17 | | #include "include/private/base/SkTo.h" |
18 | | #include "include/private/gpu/ganesh/GrTypesPriv.h" |
19 | | #include "src/core/SkSLTypeShared.h" |
20 | | #include "src/core/SkYUVAInfoLocation.h" |
21 | | #include "src/core/SkYUVMath.h" |
22 | | #include "src/gpu/KeyBuilder.h" |
23 | | #include "src/gpu/ganesh/GrSurfaceProxyView.h" |
24 | | #include "src/gpu/ganesh/GrYUVATextureProxies.h" |
25 | | #include "src/gpu/ganesh/effects/GrMatrixEffect.h" |
26 | | #include "src/gpu/ganesh/effects/GrTextureEffect.h" |
27 | | #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h" |
28 | | #include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h" |
29 | | #include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h" |
30 | | |
31 | | #include <algorithm> |
32 | | #include <array> |
33 | | #include <cmath> |
34 | | #include <cstdint> |
35 | | #include <string> |
36 | | #include <utility> |
37 | | |
38 | | struct GrShaderCaps; |
39 | | |
40 | 0 | static void border_colors(const GrYUVATextureProxies& yuvaProxies, float planeBorders[4][4]) { |
41 | 0 | float m[20]; |
42 | 0 | SkColorMatrix_RGB2YUV(yuvaProxies.yuvaInfo().yuvColorSpace(), m); |
43 | 0 | for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) { |
44 | 0 | auto [plane, channel] = yuvaProxies.yuvaLocations()[i]; |
45 | 0 | if (plane == -1) { |
46 | 0 | return; |
47 | 0 | } |
48 | 0 | auto c = static_cast<int>(channel); |
49 | 0 | planeBorders[plane][c] = m[i*5 + 4]; |
50 | 0 | } |
51 | 0 | } |
52 | | |
53 | | std::unique_ptr<GrFragmentProcessor> GrYUVtoRGBEffect::Make(const GrYUVATextureProxies& yuvaProxies, |
54 | | GrSamplerState samplerState, |
55 | | const GrCaps& caps, |
56 | | const SkMatrix& localMatrix, |
57 | | const SkRect* subset, |
58 | 0 | const SkRect* domain) { |
59 | 0 | SkASSERT(!subset || SkRect::Make(yuvaProxies.yuvaInfo().dimensions()).contains(*subset)); |
60 | |
|
61 | 0 | int numPlanes = yuvaProxies.yuvaInfo().numPlanes(); |
62 | 0 | if (!yuvaProxies.isValid()) { |
63 | 0 | return nullptr; |
64 | 0 | } |
65 | | |
66 | 0 | bool usesBorder = samplerState.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder || |
67 | 0 | samplerState.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder; |
68 | 0 | float planeBorders[4][4] = {}; |
69 | 0 | if (usesBorder) { |
70 | 0 | border_colors(yuvaProxies, planeBorders); |
71 | 0 | } |
72 | |
|
73 | 0 | bool snap[2] = {false, false}; |
74 | 0 | std::unique_ptr<GrFragmentProcessor> planeFPs[SkYUVAInfo::kMaxPlanes]; |
75 | 0 | for (int i = 0; i < numPlanes; ++i) { |
76 | 0 | bool useSubset = SkToBool(subset); |
77 | 0 | GrSurfaceProxyView view = yuvaProxies.makeView(i); |
78 | 0 | SkMatrix planeMatrix = yuvaProxies.yuvaInfo().originMatrix(); |
79 | | // The returned matrix is a view matrix but we need a local matrix. |
80 | 0 | SkAssertResult(planeMatrix.invert(&planeMatrix)); |
81 | 0 | SkRect planeSubset; |
82 | 0 | SkRect planeDomain; |
83 | 0 | bool makeLinearWithSnap = false; |
84 | 0 | auto [ssx, ssy] = yuvaProxies.yuvaInfo().planeSubsamplingFactors(i); |
85 | 0 | SkASSERT(ssx > 0 && ssx <= 4); |
86 | 0 | SkASSERT(ssy > 0 && ssy <= 2); |
87 | 0 | float scaleX = 1.f; |
88 | 0 | float scaleY = 1.f; |
89 | 0 | if (ssx > 1 || ssy > 1) { |
90 | 0 | scaleX = 1.f/ssx; |
91 | 0 | scaleY = 1.f/ssy; |
92 | | // We would want to add a translation to this matrix to handle other sitings. |
93 | 0 | SkASSERT(yuvaProxies.yuvaInfo().sitingX() == SkYUVAInfo::Siting::kCentered); |
94 | 0 | SkASSERT(yuvaProxies.yuvaInfo().sitingY() == SkYUVAInfo::Siting::kCentered); |
95 | 0 | planeMatrix.postConcat(SkMatrix::Scale(scaleX, scaleY)); |
96 | 0 | if (subset) { |
97 | 0 | planeSubset = {subset->fLeft *scaleX, |
98 | 0 | subset->fTop *scaleY, |
99 | 0 | subset->fRight *scaleX, |
100 | 0 | subset->fBottom*scaleY}; |
101 | 0 | } else { |
102 | 0 | planeSubset = SkRect::Make(view.dimensions()); |
103 | 0 | } |
104 | 0 | if (domain) { |
105 | 0 | planeDomain = {domain->fLeft *scaleX, |
106 | 0 | domain->fTop *scaleY, |
107 | 0 | domain->fRight *scaleX, |
108 | 0 | domain->fBottom*scaleY}; |
109 | 0 | } |
110 | | // If the image is not a multiple of the subsampling then the subsampled plane needs to |
111 | | // be tiled at less than its full width/height. This only matters when the mode is not |
112 | | // clamp. |
113 | 0 | if (samplerState.wrapModeX() != GrSamplerState::WrapMode::kClamp) { |
114 | 0 | int dx = (ssx*view.width() - yuvaProxies.yuvaInfo().width()); |
115 | 0 | float maxRight = view.width() - dx*scaleX; |
116 | 0 | if (planeSubset.fRight > maxRight) { |
117 | 0 | planeSubset.fRight = maxRight; |
118 | 0 | useSubset = true; |
119 | 0 | } |
120 | 0 | } |
121 | 0 | if (samplerState.wrapModeY() != GrSamplerState::WrapMode::kClamp) { |
122 | 0 | int dy = (ssy*view.height() - yuvaProxies.yuvaInfo().height()); |
123 | 0 | float maxBottom = view.height() - dy*scaleY; |
124 | 0 | if (planeSubset.fBottom > maxBottom) { |
125 | 0 | planeSubset.fBottom = maxBottom; |
126 | 0 | useSubset = true; |
127 | 0 | } |
128 | 0 | } |
129 | | // This promotion of nearest to linear filtering for UV planes exists to mimic |
130 | | // libjpeg[-turbo]'s do_fancy_upsampling option. We will filter the subsampled plane, |
131 | | // however we want to filter at a fixed point for each logical image pixel to simulate |
132 | | // nearest neighbor. |
133 | 0 | if (samplerState.filter() == GrSamplerState::Filter::kNearest) { |
134 | 0 | bool snapX = (ssx != 1), |
135 | 0 | snapY = (ssy != 1); |
136 | 0 | makeLinearWithSnap = snapX || snapY; |
137 | 0 | snap[0] |= snapX; |
138 | 0 | snap[1] |= snapY; |
139 | 0 | if (domain) { |
140 | | // The outer YUVToRGB effect will ensure sampling happens at pixel centers |
141 | | // within this plane. |
142 | 0 | planeDomain = {std::floor(planeDomain.fLeft) + 0.5f, |
143 | 0 | std::floor(planeDomain.fTop) + 0.5f, |
144 | 0 | std::floor(planeDomain.fRight) + 0.5f, |
145 | 0 | std::floor(planeDomain.fBottom) + 0.5f}; |
146 | 0 | } |
147 | 0 | } |
148 | 0 | } else { |
149 | 0 | if (subset) { |
150 | 0 | planeSubset = *subset; |
151 | 0 | } |
152 | 0 | if (domain) { |
153 | 0 | planeDomain = *domain; |
154 | 0 | } |
155 | 0 | } |
156 | 0 | if (useSubset) { |
157 | 0 | if (makeLinearWithSnap) { |
158 | | // The plane is subsampled and we have an overall subset on the image. We're |
159 | | // emulating do_fancy_upsampling using linear filtering but snapping look ups to the |
160 | | // y-plane pixel centers. Consider a logical image pixel at the edge of the subset. |
161 | | // When computing the logical pixel color value we should use a 50/50 blend of two |
162 | | // values from the subsampled plane. Depending on where the subset edge falls in |
163 | | // actual subsampled plane, one of those values may come from outside the subset. |
164 | | // Hence, we use this custom inset factory which applies the wrap mode to |
165 | | // planeSubset but allows linear filtering to read pixels from the plane that are |
166 | | // just outside planeSubset. |
167 | 0 | SkRect* domainRect = domain ? &planeDomain : nullptr; |
168 | 0 | planeFPs[i] = GrTextureEffect::MakeCustomLinearFilterInset(std::move(view), |
169 | 0 | kUnknown_SkAlphaType, |
170 | 0 | planeMatrix, |
171 | 0 | samplerState.wrapModeX(), |
172 | 0 | samplerState.wrapModeY(), |
173 | 0 | planeSubset, |
174 | 0 | domainRect, |
175 | 0 | {scaleX/2.f, scaleY/2.f}, |
176 | 0 | caps, |
177 | 0 | planeBorders[i]); |
178 | 0 | } else if (domain) { |
179 | 0 | planeFPs[i] = GrTextureEffect::MakeSubset(std::move(view), |
180 | 0 | kUnknown_SkAlphaType, |
181 | 0 | planeMatrix, |
182 | 0 | samplerState, |
183 | 0 | planeSubset, |
184 | 0 | planeDomain, |
185 | 0 | caps, |
186 | 0 | planeBorders[i]); |
187 | 0 | } else { |
188 | 0 | planeFPs[i] = GrTextureEffect::MakeSubset(std::move(view), |
189 | 0 | kUnknown_SkAlphaType, |
190 | 0 | planeMatrix, |
191 | 0 | samplerState, |
192 | 0 | planeSubset, |
193 | 0 | caps, |
194 | 0 | planeBorders[i]); |
195 | 0 | } |
196 | 0 | } else { |
197 | 0 | GrSamplerState planeSampler = samplerState; |
198 | 0 | if (makeLinearWithSnap) { |
199 | 0 | planeSampler = GrSamplerState(samplerState.wrapModeX(), |
200 | 0 | samplerState.wrapModeY(), |
201 | 0 | GrSamplerState::Filter::kLinear, |
202 | 0 | samplerState.mipmapMode()); |
203 | 0 | } |
204 | 0 | planeFPs[i] = GrTextureEffect::Make(std::move(view), |
205 | 0 | kUnknown_SkAlphaType, |
206 | 0 | planeMatrix, |
207 | 0 | planeSampler, |
208 | 0 | caps, |
209 | 0 | planeBorders[i]); |
210 | 0 | } |
211 | 0 | } |
212 | 0 | std::unique_ptr<GrFragmentProcessor> fp( |
213 | 0 | new GrYUVtoRGBEffect(planeFPs, |
214 | 0 | numPlanes, |
215 | 0 | yuvaProxies.yuvaLocations(), |
216 | 0 | snap, |
217 | 0 | yuvaProxies.yuvaInfo().yuvColorSpace())); |
218 | 0 | return GrMatrixEffect::Make(localMatrix, std::move(fp)); |
219 | 0 | } Unexecuted instantiation: GrYUVtoRGBEffect::Make(GrYUVATextureProxies const&, GrSamplerState, GrCaps const&, SkMatrix const&, SkRect const*, SkRect const*) Unexecuted instantiation: GrYUVtoRGBEffect::Make(GrYUVATextureProxies const&, GrSamplerState, GrCaps const&, SkMatrix const&, SkRect const*, SkRect const*) |
220 | | |
221 | 0 | static SkAlphaType alpha_type(const SkYUVAInfo::YUVALocations locations) { |
222 | 0 | return locations[SkYUVAInfo::YUVAChannels::kA].fPlane >= 0 ? kPremul_SkAlphaType |
223 | 0 | : kOpaque_SkAlphaType; |
224 | 0 | } |
225 | | |
226 | | GrYUVtoRGBEffect::GrYUVtoRGBEffect(std::unique_ptr<GrFragmentProcessor> planeFPs[4], |
227 | | int numPlanes, |
228 | | const SkYUVAInfo::YUVALocations& locations, |
229 | | const bool snap[2], |
230 | | SkYUVColorSpace yuvColorSpace) |
231 | | : GrFragmentProcessor(kGrYUVtoRGBEffect_ClassID, |
232 | | ModulateForClampedSamplerOptFlags(alpha_type(locations))) |
233 | | , fLocations(locations) |
234 | 0 | , fYUVColorSpace(yuvColorSpace) { |
235 | 0 | std::copy_n(snap, 2, fSnap); |
236 | |
|
237 | 0 | if (fSnap[0] || fSnap[1]) { |
238 | | // Need this so that we can access coords in SKSL to perform snapping. |
239 | 0 | this->setUsesSampleCoordsDirectly(); |
240 | 0 | for (int i = 0; i < numPlanes; ++i) { |
241 | 0 | this->registerChild(std::move(planeFPs[i]), SkSL::SampleUsage::Explicit()); |
242 | 0 | } |
243 | 0 | } else { |
244 | 0 | for (int i = 0; i < numPlanes; ++i) { |
245 | 0 | this->registerChild(std::move(planeFPs[i])); |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } |
249 | | |
250 | | #if defined(GPU_TEST_UTILS) |
251 | 0 | SkString GrYUVtoRGBEffect::onDumpInfo() const { |
252 | 0 | SkString str("("); |
253 | 0 | for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) { |
254 | 0 | str.appendf("Locations[%d]=%d %d, ", |
255 | 0 | i, fLocations[i].fPlane, static_cast<int>(fLocations[i].fChannel)); |
256 | 0 | } |
257 | 0 | str.appendf("YUVColorSpace=%d, snap=(%d, %d))", |
258 | 0 | static_cast<int>(fYUVColorSpace), fSnap[0], fSnap[1]); |
259 | 0 | return str; |
260 | 0 | } |
261 | | #endif |
262 | | |
263 | 0 | std::unique_ptr<GrFragmentProcessor::ProgramImpl> GrYUVtoRGBEffect::onMakeProgramImpl() const { |
264 | 0 | class Impl : public ProgramImpl { |
265 | 0 | public: |
266 | 0 | void emitCode(EmitArgs& args) override { |
267 | 0 | GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; |
268 | 0 | const GrYUVtoRGBEffect& yuvEffect = args.fFp.cast<GrYUVtoRGBEffect>(); |
269 | |
|
270 | 0 | int numPlanes = yuvEffect.numChildProcessors(); |
271 | |
|
272 | 0 | const char* sampleCoords = ""; |
273 | 0 | if (yuvEffect.fSnap[0] || yuvEffect.fSnap[1]) { |
274 | 0 | fragBuilder->codeAppendf("float2 snappedCoords = %s;", args.fSampleCoord); |
275 | 0 | if (yuvEffect.fSnap[0]) { |
276 | 0 | fragBuilder->codeAppend("snappedCoords.x = floor(snappedCoords.x) + 0.5;"); |
277 | 0 | } |
278 | 0 | if (yuvEffect.fSnap[1]) { |
279 | 0 | fragBuilder->codeAppend("snappedCoords.y = floor(snappedCoords.y) + 0.5;"); |
280 | 0 | } |
281 | 0 | sampleCoords = "snappedCoords"; |
282 | 0 | } |
283 | |
|
284 | 0 | fragBuilder->codeAppendf("half4 color;"); |
285 | 0 | const bool hasAlpha = yuvEffect.fLocations[SkYUVAInfo::YUVAChannels::kA].fPlane >= 0; |
286 | |
|
287 | 0 | for (int planeIdx = 0; planeIdx < numPlanes; ++planeIdx) { |
288 | 0 | std::string colorChannel; |
289 | 0 | std::string planeChannel; |
290 | 0 | for (int locIdx = 0; locIdx < (hasAlpha ? 4 : 3); ++locIdx) { |
291 | 0 | auto [yuvPlane, yuvChannel] = yuvEffect.fLocations[locIdx]; |
292 | 0 | if (yuvPlane == planeIdx) { |
293 | 0 | colorChannel.push_back("rgba"[locIdx]); |
294 | 0 | planeChannel.push_back("rgba"[static_cast<int>(yuvChannel)]); |
295 | 0 | } |
296 | 0 | } |
297 | |
|
298 | 0 | SkASSERT(colorChannel.size() == planeChannel.size()); |
299 | 0 | if (!colorChannel.empty()) { |
300 | 0 | fragBuilder->codeAppendf( |
301 | 0 | "color.%s = (%s).%s;", |
302 | 0 | colorChannel.c_str(), |
303 | 0 | this->invokeChild(planeIdx, args, sampleCoords).c_str(), |
304 | 0 | planeChannel.c_str()); |
305 | 0 | } |
306 | 0 | } |
307 | |
|
308 | 0 | if (!hasAlpha) { |
309 | 0 | fragBuilder->codeAppendf("color.a = 1;"); |
310 | 0 | } |
311 | |
|
312 | 0 | if (kIdentity_SkYUVColorSpace != yuvEffect.fYUVColorSpace) { |
313 | 0 | fColorSpaceMatrixVar = args.fUniformHandler->addUniform(&yuvEffect, |
314 | 0 | kFragment_GrShaderFlag, SkSLType::kHalf3x3, "colorSpaceMatrix"); |
315 | 0 | fColorSpaceTranslateVar = args.fUniformHandler->addUniform(&yuvEffect, |
316 | 0 | kFragment_GrShaderFlag, SkSLType::kHalf3, "colorSpaceTranslate"); |
317 | 0 | fragBuilder->codeAppendf( |
318 | 0 | "color.rgb = saturate(color.rgb * %s + %s);", |
319 | 0 | args.fUniformHandler->getUniformCStr(fColorSpaceMatrixVar), |
320 | 0 | args.fUniformHandler->getUniformCStr(fColorSpaceTranslateVar)); |
321 | 0 | } |
322 | 0 | if (hasAlpha) { |
323 | | // premultiply alpha |
324 | 0 | fragBuilder->codeAppendf("color.rgb *= color.a;"); |
325 | 0 | } |
326 | 0 | fragBuilder->codeAppendf("return color;"); |
327 | 0 | } Unexecuted instantiation: GrYUVtoRGBEffect.cpp:GrYUVtoRGBEffect::onMakeProgramImpl() const::Impl::emitCode(GrFragmentProcessor::ProgramImpl::EmitArgs&) Unexecuted instantiation: GrYUVtoRGBEffect.cpp:GrYUVtoRGBEffect::onMakeProgramImpl() const::Impl::emitCode(GrFragmentProcessor::ProgramImpl::EmitArgs&) |
328 | |
|
329 | 0 | private: |
330 | 0 | void onSetData(const GrGLSLProgramDataManager& pdman, |
331 | 0 | const GrFragmentProcessor& proc) override { |
332 | 0 | const GrYUVtoRGBEffect& yuvEffect = proc.cast<GrYUVtoRGBEffect>(); |
333 | |
|
334 | 0 | if (yuvEffect.fYUVColorSpace != kIdentity_SkYUVColorSpace) { |
335 | 0 | SkASSERT(fColorSpaceMatrixVar.isValid()); |
336 | 0 | float yuvM[20]; |
337 | 0 | SkColorMatrix_YUV2RGB(yuvEffect.fYUVColorSpace, yuvM); |
338 | | // We drop the fourth column entirely since the transformation |
339 | | // should not depend on alpha. The fifth column is sent as a separate |
340 | | // vector. The fourth row is also dropped entirely because alpha should |
341 | | // never be modified. |
342 | 0 | SkASSERT(yuvM[3] == 0 && yuvM[8] == 0 && yuvM[13] == 0 && yuvM[18] == 1); |
343 | 0 | SkASSERT(yuvM[15] == 0 && yuvM[16] == 0 && yuvM[17] == 0 && yuvM[19] == 0); |
344 | 0 | float mtx[9] = { |
345 | 0 | yuvM[ 0], yuvM[ 1], yuvM[ 2], |
346 | 0 | yuvM[ 5], yuvM[ 6], yuvM[ 7], |
347 | 0 | yuvM[10], yuvM[11], yuvM[12], |
348 | 0 | }; |
349 | 0 | float v[3] = {yuvM[4], yuvM[9], yuvM[14]}; |
350 | 0 | pdman.setMatrix3f(fColorSpaceMatrixVar, mtx); |
351 | 0 | pdman.set3fv(fColorSpaceTranslateVar, 1, v); |
352 | 0 | } |
353 | 0 | } Unexecuted instantiation: GrYUVtoRGBEffect.cpp:GrYUVtoRGBEffect::onMakeProgramImpl() const::Impl::onSetData(GrGLSLProgramDataManager const&, GrFragmentProcessor const&) Unexecuted instantiation: GrYUVtoRGBEffect.cpp:GrYUVtoRGBEffect::onMakeProgramImpl() const::Impl::onSetData(GrGLSLProgramDataManager const&, GrFragmentProcessor const&) |
354 | |
|
355 | 0 | UniformHandle fColorSpaceMatrixVar; |
356 | 0 | UniformHandle fColorSpaceTranslateVar; |
357 | 0 | }; |
358 | |
|
359 | 0 | return std::make_unique<Impl>(); |
360 | 0 | } |
361 | | |
362 | 0 | void GrYUVtoRGBEffect::onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const { |
363 | 0 | uint32_t packed = 0; |
364 | 0 | int i = 0; |
365 | 0 | for (auto [plane, channel] : fLocations) { |
366 | 0 | if (plane < 0) { |
367 | 0 | continue; |
368 | 0 | } |
369 | | |
370 | 0 | uint8_t chann = static_cast<int>(channel); |
371 | |
|
372 | 0 | SkASSERT(plane < 4 && chann < 4); |
373 | |
|
374 | 0 | packed |= (plane | (chann << 2)) << (i++ * 4); |
375 | 0 | } |
376 | 0 | if (fYUVColorSpace == kIdentity_SkYUVColorSpace) { |
377 | 0 | packed |= 1 << 16; |
378 | 0 | } |
379 | 0 | if (fSnap[0]) { |
380 | 0 | packed |= 1 << 17; |
381 | 0 | } |
382 | 0 | if (fSnap[1]) { |
383 | 0 | packed |= 1 << 18; |
384 | 0 | } |
385 | 0 | b->add32(packed); |
386 | 0 | } Unexecuted instantiation: GrYUVtoRGBEffect::onAddToKey(GrShaderCaps const&, skgpu::KeyBuilder*) const Unexecuted instantiation: GrYUVtoRGBEffect::onAddToKey(GrShaderCaps const&, skgpu::KeyBuilder*) const |
387 | | |
388 | 0 | bool GrYUVtoRGBEffect::onIsEqual(const GrFragmentProcessor& other) const { |
389 | 0 | const GrYUVtoRGBEffect& that = other.cast<GrYUVtoRGBEffect>(); |
390 | |
|
391 | 0 | return fLocations == that.fLocations && |
392 | 0 | std::equal(fSnap, fSnap + 2, that.fSnap) && |
393 | 0 | fYUVColorSpace == that.fYUVColorSpace; |
394 | 0 | } |
395 | | |
396 | | GrYUVtoRGBEffect::GrYUVtoRGBEffect(const GrYUVtoRGBEffect& src) |
397 | | : GrFragmentProcessor(src) |
398 | | , fLocations((src.fLocations)) |
399 | 0 | , fYUVColorSpace(src.fYUVColorSpace) { |
400 | 0 | std::copy_n(src.fSnap, 2, fSnap); |
401 | 0 | } |
402 | | |
403 | 0 | std::unique_ptr<GrFragmentProcessor> GrYUVtoRGBEffect::clone() const { |
404 | 0 | return std::unique_ptr<GrFragmentProcessor>(new GrYUVtoRGBEffect(*this)); |
405 | 0 | } |