1 /*
2 * Copyright 2013 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/core/SkDistanceFieldGen.h"
9 #include "src/gpu/GrCaps.h"
10 #include "src/gpu/GrShaderCaps.h"
11 #include "src/gpu/GrTexture.h"
12 #include "src/gpu/effects/GrAtlasedShaderHelpers.h"
13 #include "src/gpu/effects/GrDistanceFieldGeoProc.h"
14 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
15 #include "src/gpu/glsl/GrGLSLProgramDataManager.h"
16 #include "src/gpu/glsl/GrGLSLUniformHandler.h"
17 #include "src/gpu/glsl/GrGLSLVarying.h"
18 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
19
20 // Assuming a radius of a little less than the diagonal of the fragment
21 #define SK_DistanceFieldAAFactor "0.65"
22
23 class GrDistanceFieldA8TextGeoProc::Impl : public ProgramImpl {
24 public:
25 void setData(const GrGLSLProgramDataManager& pdman,
26 const GrShaderCaps& shaderCaps,
27 const GrGeometryProcessor& geomProc) override {
28 const GrDistanceFieldA8TextGeoProc& dfa8gp = geomProc.cast<GrDistanceFieldA8TextGeoProc>();
29
30 #ifdef SK_GAMMA_APPLY_TO_A8
31 float distanceAdjust = dfa8gp.fDistanceAdjust;
32 if (distanceAdjust != fDistanceAdjust) {
33 fDistanceAdjust = distanceAdjust;
34 pdman.set1f(fDistanceAdjustUni, distanceAdjust);
35 }
36 #endif
37
38 const SkISize& atlasDimensions = dfa8gp.fAtlasDimensions;
39 SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
40
41 if (fAtlasDimensions != atlasDimensions) {
42 pdman.set2f(fAtlasDimensionsInvUniform,
43 1.0f / atlasDimensions.fWidth,
44 1.0f / atlasDimensions.fHeight);
45 fAtlasDimensions = atlasDimensions;
46 }
47 SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dfa8gp.fLocalMatrix, &fLocalMatrix);
48 }
49
50 private:
51 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
52 const GrDistanceFieldA8TextGeoProc& dfTexEffect =
53 args.fGeomProc.cast<GrDistanceFieldA8TextGeoProc>();
54 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
55
56 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
57 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
58 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
59
60 // emit attributes
61 varyingHandler->emitAttributes(dfTexEffect);
62
63 const char* atlasDimensionsInvName;
64 fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
65 kVertex_GrShaderFlag,
66 kFloat2_GrSLType,
67 "AtlasDimensionsInv",
68 &atlasDimensionsInvName);
69 #ifdef SK_GAMMA_APPLY_TO_A8
70 // adjust based on gamma
71 const char* distanceAdjustUniName = nullptr;
72 // width, height, 1/(3*width)
73 fDistanceAdjustUni = uniformHandler->addUniform(nullptr, kFragment_GrShaderFlag,
74 kHalf_GrSLType, "DistanceAdjust",
75 &distanceAdjustUniName);
76 #endif
77
78 // Setup pass through color
79 fragBuilder->codeAppendf("half4 %s;\n", args.fOutputColor);
80 varyingHandler->addPassThroughAttribute(dfTexEffect.fInColor.asShaderVar(),
81 args.fOutputColor);
82
83 // Setup position
84 gpArgs->fPositionVar = dfTexEffect.fInPosition.asShaderVar();
85 WriteLocalCoord(vertBuilder,
86 uniformHandler,
87 *args.fShaderCaps,
88 gpArgs,
89 gpArgs->fPositionVar,
90 dfTexEffect.fLocalMatrix,
91 &fLocalMatrixUniform);
92
93 // add varyings
94 GrGLSLVarying uv, texIdx, st;
95 append_index_uv_varyings(args,
96 dfTexEffect.numTextureSamplers(),
97 dfTexEffect.fInTextureCoords.name(),
98 atlasDimensionsInvName,
99 &uv,
100 &texIdx,
101 &st);
102
103 bool isUniformScale = (dfTexEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
104 kUniformScale_DistanceFieldEffectMask;
105 bool isSimilarity = SkToBool(dfTexEffect.fFlags & kSimilarity_DistanceFieldEffectFlag );
106 bool isGammaCorrect = SkToBool(dfTexEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
107 bool isAliased = SkToBool(dfTexEffect.fFlags & kAliased_DistanceFieldEffectFlag );
108
109 // Use highp to work around aliasing issues
110 fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn());
111 fragBuilder->codeAppend("half4 texColor;");
112 append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
113 texIdx, "uv", "texColor");
114
115 fragBuilder->codeAppend("half distance = "
116 SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
117 #ifdef SK_GAMMA_APPLY_TO_A8
118 // adjust width based on gamma
119 fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName);
120 #endif
121
122 fragBuilder->codeAppend("half afwidth;");
123 if (isUniformScale) {
124 // For uniform scale, we adjust for the effect of the transformation on the distance
125 // by using the length of the gradient of the t coordinate in the y direction.
126 // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
127
128 // this gives us a smooth step across approximately one fragment
129 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
130 fragBuilder->codeAppendf(
131 "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdy(%s.y)));", st.fsIn());
132 } else {
133 fragBuilder->codeAppendf(
134 "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdx(%s.x)));", st.fsIn());
135 }
136 } else if (isSimilarity) {
137 // For similarity transform, we adjust the effect of the transformation on the distance
138 // by using the length of the gradient of the texture coordinates. We use st coordinates
139 // to ensure we're mapping 1:1 from texel space to pixel space.
140 // We use the y gradient because there is a bug in the Mali 400 in the x direction.
141
142 // this gives us a smooth step across approximately one fragment
143 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
144 fragBuilder->codeAppendf("half st_grad_len = length(half2(dFdy(%s)));", st.fsIn());
145 } else {
146 fragBuilder->codeAppendf("half st_grad_len = length(half2(dFdx(%s)));", st.fsIn());
147 }
148 fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
149 } else {
150 // For general transforms, to determine the amount of correction we multiply a unit
151 // vector pointing along the SDF gradient direction by the Jacobian of the st coords
152 // (which is the inverse transform for this fragment) and take the length of the result.
153 fragBuilder->codeAppend("half2 dist_grad = half2(float2(dFdx(distance), "
154 "dFdy(distance)));");
155 // the length of the gradient may be 0, so we need to check for this
156 // this also compensates for the Adreno, which likes to drop tiles on division by 0
157 fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);");
158 fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
159 fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);");
160 fragBuilder->codeAppend("} else {");
161 fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));");
162 fragBuilder->codeAppend("}");
163
164 fragBuilder->codeAppendf("half2 Jdx = half2(dFdx(%s));", st.fsIn());
165 fragBuilder->codeAppendf("half2 Jdy = half2(dFdy(%s));", st.fsIn());
166 fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
167 fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
168
169 // this gives us a smooth step across approximately one fragment
170 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
171 }
172
173 if (isAliased) {
174 fragBuilder->codeAppend("half val = distance > 0 ? 1.0 : 0.0;");
175 } else if (isGammaCorrect) {
176 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
177 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want
178 // distance mapped linearly to coverage, so use a linear step:
179 fragBuilder->codeAppend(
180 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
181 } else {
182 fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
183 }
184
185 fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
186 }
187
188 private:
189 #ifdef SK_GAMMA_APPLY_TO_A8
190 float fDistanceAdjust = -1.f;
191 #endif
192 SkISize fAtlasDimensions = {-1, -1};
193 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix();
194
195 UniformHandle fDistanceAdjustUni;
196 UniformHandle fAtlasDimensionsInvUniform;
197 UniformHandle fLocalMatrixUniform;
198
199 using INHERITED = ProgramImpl;
200 };
201
202 ///////////////////////////////////////////////////////////////////////////////
203
GrDistanceFieldA8TextGeoProc(const GrShaderCaps& caps, const GrSurfaceProxyView* views, int numViews, GrSamplerState params, float distanceAdjust, uint32_t flags, const SkMatrix& localMatrix)204 GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(const GrShaderCaps& caps,
205 const GrSurfaceProxyView* views,
206 int numViews,
207 GrSamplerState params,
208 #ifdef SK_GAMMA_APPLY_TO_A8
209 float distanceAdjust,
210 #endif
211 uint32_t flags,
212 const SkMatrix& localMatrix)
213 : INHERITED(kGrDistanceFieldA8TextGeoProc_ClassID)
214 , fLocalMatrix(localMatrix)
215 , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
216 #ifdef SK_GAMMA_APPLY_TO_A8
217 , fDistanceAdjust(distanceAdjust)
218 #endif
219 {
220 SkASSERT(numViews <= kMaxTextures);
221 SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
222
223 if (flags & kPerspective_DistanceFieldEffectFlag) {
224 fInPosition = {"inPosition", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
225 } else {
226 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
227 }
228 fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType };
229 fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
230 caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
231 this->setVertexAttributes(&fInPosition, 3);
232
233 if (numViews) {
234 fAtlasDimensions = views[0].proxy()->dimensions();
235 }
236 for (int i = 0; i < numViews; ++i) {
237 const GrSurfaceProxy* proxy = views[i].proxy();
238 SkASSERT(proxy);
239 SkASSERT(proxy->dimensions() == fAtlasDimensions);
240 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
241 }
242 this->setTextureSamplerCnt(numViews);
243 }
244
addNewViews(const GrSurfaceProxyView* views, int numViews, GrSamplerState params)245 void GrDistanceFieldA8TextGeoProc::addNewViews(const GrSurfaceProxyView* views,
246 int numViews,
247 GrSamplerState params) {
248 SkASSERT(numViews <= kMaxTextures);
249 // Just to make sure we don't try to add too many proxies
250 numViews = std::min(numViews, kMaxTextures);
251
252 if (!fTextureSamplers[0].isInitialized()) {
253 fAtlasDimensions = views[0].proxy()->dimensions();
254 }
255
256 for (int i = 0; i < numViews; ++i) {
257 const GrSurfaceProxy* proxy = views[i].proxy();
258 SkASSERT(proxy);
259 SkASSERT(proxy->dimensions() == fAtlasDimensions);
260 if (!fTextureSamplers[i].isInitialized()) {
261 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
262 }
263 }
264 this->setTextureSamplerCnt(numViews);
265 }
266
getShaderDfxInfo() const267 SkString GrDistanceFieldA8TextGeoProc::getShaderDfxInfo() const
268 {
269 SkString format;
270 format.printf("ShaderDfx_GrDistanceFieldA8TextGeoProc_%d_%d_%d_%d_%d", fFlags, numTextureSamplers(),
271 fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective());
272 return format;
273 }
274
addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const275 void GrDistanceFieldA8TextGeoProc::addToKey(const GrShaderCaps& caps,
276 GrProcessorKeyBuilder* b) const {
277 uint32_t key = 0;
278 key |= fFlags;
279 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 16;
280 b->add32(key);
281 b->add32(this->numTextureSamplers());
282 }
283
makeProgramImpl( const GrShaderCaps&) const284 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldA8TextGeoProc::makeProgramImpl(
285 const GrShaderCaps&) const {
286 return std::make_unique<Impl>();
287 }
288
289 ///////////////////////////////////////////////////////////////////////////////
290
291 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc);
292
293 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData* d)294 GrGeometryProcessor* GrDistanceFieldA8TextGeoProc::TestCreate(GrProcessorTestData* d) {
295 auto [view, ct, at] = d->randomAlphaOnlyView();
296
297 GrSamplerState::WrapMode wrapModes[2];
298 GrTest::TestWrapModes(d->fRandom, wrapModes);
299 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
300 ? GrSamplerState::Filter::kLinear
301 : GrSamplerState::Filter::kNearest);
302
303 uint32_t flags = 0;
304 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
305 if (flags & kSimilarity_DistanceFieldEffectFlag) {
306 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
307 }
308 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
309 #ifdef SK_GAMMA_APPLY_TO_A8
310 float lum = d->fRandom->nextF();
311 #endif
312 return GrDistanceFieldA8TextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
313 &view, 1,
314 samplerState,
315 #ifdef SK_GAMMA_APPLY_TO_A8
316 lum,
317 #endif
318 flags, localMatrix);
319 }
320 #endif
321
322 ///////////////////////////////////////////////////////////////////////////////
323
324 class GrDistanceFieldPathGeoProc::Impl : public ProgramImpl {
325 public:
326 void setData(const GrGLSLProgramDataManager& pdman,
327 const GrShaderCaps& shaderCaps,
328 const GrGeometryProcessor& geomProc) override {
329 const GrDistanceFieldPathGeoProc& dfpgp = geomProc.cast<GrDistanceFieldPathGeoProc>();
330
331 // We always set the matrix uniform; it's either used to transform from local to device
332 // for the output position, or from device to local for the local coord variable.
333 SetTransform(pdman, shaderCaps, fMatrixUniform, dfpgp.fMatrix, &fMatrix);
334
335 const SkISize& atlasDimensions = dfpgp.fAtlasDimensions;
336 SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
337 if (fAtlasDimensions != atlasDimensions) {
338 pdman.set2f(fAtlasDimensionsInvUniform,
339 1.0f / atlasDimensions.fWidth,
340 1.0f / atlasDimensions.fHeight);
341 fAtlasDimensions = atlasDimensions;
342 }
343 }
344
345 private:
346 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
347 const GrDistanceFieldPathGeoProc& dfPathEffect =
348 args.fGeomProc.cast<GrDistanceFieldPathGeoProc>();
349
350 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
351
352 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
353 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
354 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
355
356 // emit attributes
357 varyingHandler->emitAttributes(dfPathEffect);
358
359 const char* atlasDimensionsInvName;
360 fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
361 kVertex_GrShaderFlag,
362 kFloat2_GrSLType,
363 "AtlasDimensionsInv",
364 &atlasDimensionsInvName);
365
366 GrGLSLVarying uv, texIdx, st;
367 append_index_uv_varyings(args,
368 dfPathEffect.numTextureSamplers(),
369 dfPathEffect.fInTextureCoords.name(),
370 atlasDimensionsInvName,
371 &uv,
372 &texIdx,
373 &st);
374
375 // setup pass through color
376 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
377 varyingHandler->addPassThroughAttribute(dfPathEffect.fInColor.asShaderVar(),
378 args.fOutputColor);
379
380 if (dfPathEffect.fMatrix.hasPerspective()) {
381 // Setup position (output position is transformed, local coords are pass through)
382 WriteOutputPosition(vertBuilder,
383 uniformHandler,
384 *args.fShaderCaps,
385 gpArgs,
386 dfPathEffect.fInPosition.name(),
387 dfPathEffect.fMatrix,
388 &fMatrixUniform);
389 gpArgs->fLocalCoordVar = dfPathEffect.fInPosition.asShaderVar();
390 } else {
391 // Setup position (output position is pass through, local coords are transformed)
392 WriteOutputPosition(vertBuilder, gpArgs, dfPathEffect.fInPosition.name());
393 WriteLocalCoord(vertBuilder,
394 uniformHandler,
395 *args.fShaderCaps,
396 gpArgs,
397 dfPathEffect.fInPosition.asShaderVar(),
398 dfPathEffect.fMatrix,
399 &fMatrixUniform);
400 }
401
402 // Use highp to work around aliasing issues
403 fragBuilder->codeAppendf("float2 uv = %s;", uv.fsIn());
404 fragBuilder->codeAppend("half4 texColor;");
405 append_multitexture_lookup(args, dfPathEffect.numTextureSamplers(), texIdx, "uv",
406 "texColor");
407
408 fragBuilder->codeAppend("half distance = "
409 SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
410
411 fragBuilder->codeAppend("half afwidth;");
412 bool isUniformScale = (dfPathEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
413 kUniformScale_DistanceFieldEffectMask;
414 bool isSimilarity = SkToBool(dfPathEffect.fFlags & kSimilarity_DistanceFieldEffectFlag );
415 bool isGammaCorrect = SkToBool(dfPathEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
416 if (isUniformScale) {
417 // For uniform scale, we adjust for the effect of the transformation on the distance
418 // by using the length of the gradient of the t coordinate in the y direction.
419 // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
420
421 // this gives us a smooth step across approximately one fragment
422 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
423 fragBuilder->codeAppendf(
424 "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdy(%s.y)));", st.fsIn());
425 } else {
426 fragBuilder->codeAppendf(
427 "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdx(%s.x)));", st.fsIn());
428 }
429 } else if (isSimilarity) {
430 // For similarity transform, we adjust the effect of the transformation on the distance
431 // by using the length of the gradient of the texture coordinates. We use st coordinates
432 // to ensure we're mapping 1:1 from texel space to pixel space.
433
434 // this gives us a smooth step across approximately one fragment
435 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
436 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdy(%s)));", st.fsIn());
437 } else {
438 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdx(%s)));", st.fsIn());
439 }
440 fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
441 } else {
442 // For general transforms, to determine the amount of correction we multiply a unit
443 // vector pointing along the SDF gradient direction by the Jacobian of the st coords
444 // (which is the inverse transform for this fragment) and take the length of the result.
445 fragBuilder->codeAppend("half2 dist_grad = half2(dFdx(distance), "
446 "dFdy(distance));");
447 // the length of the gradient may be 0, so we need to check for this
448 // this also compensates for the Adreno, which likes to drop tiles on division by 0
449 fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);");
450 fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
451 fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);");
452 fragBuilder->codeAppend("} else {");
453 fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));");
454 fragBuilder->codeAppend("}");
455
456 fragBuilder->codeAppendf("half2 Jdx = half2(dFdx(%s));", st.fsIn());
457 fragBuilder->codeAppendf("half2 Jdy = half2(dFdy(%s));", st.fsIn());
458 fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
459 fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
460
461 // this gives us a smooth step across approximately one fragment
462 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
463 }
464 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
465 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
466 // mapped linearly to coverage, so use a linear step:
467 if (isGammaCorrect) {
468 fragBuilder->codeAppend(
469 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
470 } else {
471 fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
472 }
473
474 fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
475 }
476
477 SkMatrix fMatrix; // view matrix if perspective, local matrix otherwise
478 UniformHandle fMatrixUniform;
479
480 SkISize fAtlasDimensions;
481 UniformHandle fAtlasDimensionsInvUniform;
482
483 using INHERITED = ProgramImpl;
484 };
485
486 ///////////////////////////////////////////////////////////////////////////////
487
GrDistanceFieldPathGeoProc(const GrShaderCaps& caps, const SkMatrix& matrix, bool wideColor, const GrSurfaceProxyView* views, int numViews, GrSamplerState params, uint32_t flags)488 GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
489 const SkMatrix& matrix,
490 bool wideColor,
491 const GrSurfaceProxyView* views,
492 int numViews,
493 GrSamplerState params,
494 uint32_t flags)
495 : INHERITED(kGrDistanceFieldPathGeoProc_ClassID)
496 , fMatrix(matrix)
497 , fFlags(flags & kNonLCD_DistanceFieldEffectMask) {
498 SkASSERT(numViews <= kMaxTextures);
499 SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
500
501 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
502 fInColor = MakeColorAttribute("inColor", wideColor);
503 fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
504 caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
505 this->setVertexAttributes(&fInPosition, 3);
506
507 if (numViews) {
508 fAtlasDimensions = views[0].proxy()->dimensions();
509 }
510
511 for (int i = 0; i < numViews; ++i) {
512 const GrSurfaceProxy* proxy = views[i].proxy();
513 SkASSERT(proxy);
514 SkASSERT(proxy->dimensions() == fAtlasDimensions);
515 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
516 }
517 this->setTextureSamplerCnt(numViews);
518 }
519
addNewViews(const GrSurfaceProxyView* views, int numViews, GrSamplerState params)520 void GrDistanceFieldPathGeoProc::addNewViews(const GrSurfaceProxyView* views,
521 int numViews,
522 GrSamplerState params) {
523 SkASSERT(numViews <= kMaxTextures);
524 // Just to make sure we don't try to add too many proxies
525 numViews = std::min(numViews, kMaxTextures);
526
527 if (!fTextureSamplers[0].isInitialized()) {
528 fAtlasDimensions = views[0].proxy()->dimensions();
529 }
530
531 for (int i = 0; i < numViews; ++i) {
532 const GrSurfaceProxy* proxy = views[i].proxy();
533 SkASSERT(proxy);
534 SkASSERT(proxy->dimensions() == fAtlasDimensions);
535 if (!fTextureSamplers[i].isInitialized()) {
536 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
537 }
538 }
539 this->setTextureSamplerCnt(numViews);
540 }
541
getShaderDfxInfo() const542 SkString GrDistanceFieldPathGeoProc::getShaderDfxInfo() const
543 {
544 SkString format;
545 format.printf("ShaderDfx_GrDistanceFieldPathGeoProc_%d_%d_%d_%d_%d", fFlags, numTextureSamplers(),
546 fMatrix.isIdentity(), fMatrix.isScaleTranslate(), fMatrix.hasPerspective());
547 return format;
548 }
549
addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const550 void GrDistanceFieldPathGeoProc::addToKey(const GrShaderCaps& caps,
551 GrProcessorKeyBuilder* b) const {
552 uint32_t key = fFlags;
553 key |= ProgramImpl::ComputeMatrixKey(caps, fMatrix) << 16;
554 key |= fMatrix.hasPerspective() << (16 + ProgramImpl::kMatrixKeyBits);
555 b->add32(key);
556 b->add32(this->numTextureSamplers());
557 }
558
makeProgramImpl( const GrShaderCaps&) const559 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldPathGeoProc::makeProgramImpl(
560 const GrShaderCaps&) const {
561 return std::make_unique<Impl>();
562 }
563
564 ///////////////////////////////////////////////////////////////////////////////
565
566 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc);
567
568 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData* d)569 GrGeometryProcessor* GrDistanceFieldPathGeoProc::TestCreate(GrProcessorTestData* d) {
570 auto [view, ct, at] = d->randomAlphaOnlyView();
571
572 GrSamplerState::WrapMode wrapModes[2];
573 GrTest::TestWrapModes(d->fRandom, wrapModes);
574 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
575 ? GrSamplerState::Filter::kLinear
576 : GrSamplerState::Filter::kNearest);
577
578 uint32_t flags = 0;
579 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
580 if (flags & kSimilarity_DistanceFieldEffectFlag) {
581 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
582 }
583 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
584 bool wideColor = d->fRandom->nextBool();
585 return GrDistanceFieldPathGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
586 localMatrix,
587 wideColor,
588 &view, 1,
589 samplerState,
590 flags);
591 }
592 #endif
593
594 ///////////////////////////////////////////////////////////////////////////////
595
596 class GrDistanceFieldLCDTextGeoProc::Impl : public ProgramImpl {
597 public:
598 void setData(const GrGLSLProgramDataManager& pdman,
599 const GrShaderCaps& shaderCaps,
600 const GrGeometryProcessor& geomProc) override {
601 SkASSERT(fDistanceAdjustUni.isValid());
602
603 const GrDistanceFieldLCDTextGeoProc& dflcd = geomProc.cast<GrDistanceFieldLCDTextGeoProc>();
604 GrDistanceFieldLCDTextGeoProc::DistanceAdjust wa = dflcd.fDistanceAdjust;
605 if (wa != fDistanceAdjust) {
606 pdman.set3f(fDistanceAdjustUni, wa.fR, wa.fG, wa.fB);
607 fDistanceAdjust = wa;
608 }
609
610 const SkISize& atlasDimensions = dflcd.fAtlasDimensions;
611 SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
612 if (fAtlasDimensions != atlasDimensions) {
613 pdman.set2f(fAtlasDimensionsInvUniform,
614 1.0f / atlasDimensions.fWidth,
615 1.0f / atlasDimensions.fHeight);
616 fAtlasDimensions = atlasDimensions;
617 }
618 SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dflcd.fLocalMatrix, &fLocalMatrix);
619 }
620
621 private:
622 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
623 const GrDistanceFieldLCDTextGeoProc& dfTexEffect =
624 args.fGeomProc.cast<GrDistanceFieldLCDTextGeoProc>();
625
626 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
627 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
628 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
629
630 // emit attributes
631 varyingHandler->emitAttributes(dfTexEffect);
632
633 const char* atlasDimensionsInvName;
634 fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
635 kVertex_GrShaderFlag,
636 kFloat2_GrSLType,
637 "AtlasDimensionsInv",
638 &atlasDimensionsInvName);
639
640 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
641
642 // setup pass through color
643 fragBuilder->codeAppendf("half4 %s;\n", args.fOutputColor);
644 varyingHandler->addPassThroughAttribute(dfTexEffect.fInColor.asShaderVar(),
645 args.fOutputColor);
646
647 // Setup position
648 gpArgs->fPositionVar = dfTexEffect.fInPosition.asShaderVar();
649 WriteLocalCoord(vertBuilder,
650 uniformHandler,
651 *args.fShaderCaps,
652 gpArgs,
653 dfTexEffect.fInPosition.asShaderVar(),
654 dfTexEffect.fLocalMatrix,
655 &fLocalMatrixUniform);
656
657 // set up varyings
658 GrGLSLVarying uv, texIdx, st;
659 append_index_uv_varyings(args,
660 dfTexEffect.numTextureSamplers(),
661 dfTexEffect.fInTextureCoords.name(),
662 atlasDimensionsInvName,
663 &uv,
664 &texIdx,
665 &st);
666
667 GrGLSLVarying delta(kFloat_GrSLType);
668 varyingHandler->addVarying("Delta", &delta);
669 if (dfTexEffect.fFlags & kBGR_DistanceFieldEffectFlag) {
670 vertBuilder->codeAppendf("%s = -%s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
671 } else {
672 vertBuilder->codeAppendf("%s = %s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
673 }
674
675 // add frag shader code
676 bool isUniformScale = (dfTexEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
677 kUniformScale_DistanceFieldEffectMask;
678 bool isSimilarity = SkToBool(dfTexEffect.fFlags & kSimilarity_DistanceFieldEffectFlag );
679 bool isGammaCorrect = SkToBool(dfTexEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
680
681 // create LCD offset adjusted by inverse of transform
682 // Use highp to work around aliasing issues
683 fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn());
684
685 if (isUniformScale) {
686 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
687 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdy(%s.y)));", st.fsIn());
688 } else {
689 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdx(%s.x)));", st.fsIn());
690 }
691 fragBuilder->codeAppendf("half2 offset = half2(half(st_grad_len*%s), 0.0);",
692 delta.fsIn());
693 } else if (isSimilarity) {
694 // For a similarity matrix with rotation, the gradient will not be aligned
695 // with the texel coordinate axes, so we need to calculate it.
696 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
697 // We use dFdy instead and rotate -90 degrees to get the gradient in the x
698 // direction.
699 fragBuilder->codeAppendf("half2 st_grad = half2(dFdy(%s));", st.fsIn());
700 fragBuilder->codeAppendf("half2 offset = half2(%s*float2(st_grad.y, -st_grad.x));",
701 delta.fsIn());
702 } else {
703 fragBuilder->codeAppendf("half2 st_grad = half2(dFdx(%s));", st.fsIn());
704 fragBuilder->codeAppendf("half2 offset = half(%s)*st_grad;", delta.fsIn());
705 }
706 fragBuilder->codeAppend("half st_grad_len = length(st_grad);");
707 } else {
708 fragBuilder->codeAppendf("half2 st = half2(%s);\n", st.fsIn());
709
710 fragBuilder->codeAppend("half2 Jdx = half2(dFdx(st));");
711 fragBuilder->codeAppend("half2 Jdy = half2(dFdy(st));");
712 fragBuilder->codeAppendf("half2 offset = half2(half(%s))*Jdx;", delta.fsIn());
713 }
714
715 // sample the texture by index
716 fragBuilder->codeAppend("half4 texColor;");
717 append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
718 texIdx, "uv", "texColor");
719
720 // green is distance to uv center
721 fragBuilder->codeAppend("half3 distance;");
722 fragBuilder->codeAppend("distance.y = texColor.r;");
723 // red is distance to left offset
724 fragBuilder->codeAppend("half2 uv_adjusted = half2(uv) - offset;");
725 append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
726 texIdx, "uv_adjusted", "texColor");
727 fragBuilder->codeAppend("distance.x = texColor.r;");
728 // blue is distance to right offset
729 fragBuilder->codeAppend("uv_adjusted = half2(uv) + offset;");
730 append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
731 texIdx, "uv_adjusted", "texColor");
732 fragBuilder->codeAppend("distance.z = texColor.r;");
733
734 fragBuilder->codeAppend("distance = "
735 "half3(" SK_DistanceFieldMultiplier ")*(distance - half3(" SK_DistanceFieldThreshold"));");
736
737 // adjust width based on gamma
738 const char* distanceAdjustUniName = nullptr;
739 fDistanceAdjustUni = uniformHandler->addUniform(nullptr, kFragment_GrShaderFlag,
740 kHalf3_GrSLType, "DistanceAdjust",
741 &distanceAdjustUniName);
742 fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName);
743
744 // To be strictly correct, we should compute the anti-aliasing factor separately
745 // for each color component. However, this is only important when using perspective
746 // transformations, and even then using a single factor seems like a reasonable
747 // trade-off between quality and speed.
748 fragBuilder->codeAppend("half afwidth;");
749 if (isSimilarity) {
750 // For similarity transform (uniform scale-only is a subset of this), we adjust for the
751 // effect of the transformation on the distance by using the length of the gradient of
752 // the texture coordinates. We use st coordinates to ensure we're mapping 1:1 from texel
753 // space to pixel space.
754
755 // this gives us a smooth step across approximately one fragment
756 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*st_grad_len;");
757 } else {
758 // For general transforms, to determine the amount of correction we multiply a unit
759 // vector pointing along the SDF gradient direction by the Jacobian of the st coords
760 // (which is the inverse transform for this fragment) and take the length of the result.
761 fragBuilder->codeAppend("half2 dist_grad = half2(half(dFdx(distance.r)), "
762 "half(dFdy(distance.r)));");
763 // the length of the gradient may be 0, so we need to check for this
764 // this also compensates for the Adreno, which likes to drop tiles on division by 0
765 fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);");
766 fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
767 fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);");
768 fragBuilder->codeAppend("} else {");
769 fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));");
770 fragBuilder->codeAppend("}");
771 fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
772 fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
773
774 // this gives us a smooth step across approximately one fragment
775 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
776 }
777
778 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
779 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
780 // mapped linearly to coverage, so use a linear step:
781 if (isGammaCorrect) {
782 fragBuilder->codeAppendf("half4 %s = "
783 "half4(saturate((distance + half3(afwidth)) / half3(2.0 * afwidth)), 1.0);",
784 args.fOutputCoverage);
785 } else {
786 fragBuilder->codeAppendf(
787 "half4 %s = half4(smoothstep(half3(-afwidth), half3(afwidth), distance), 1.0);",
788 args.fOutputCoverage);
789 }
790 }
791
792 private:
793 DistanceAdjust fDistanceAdjust = DistanceAdjust::Make(1.0f, 1.0f, 1.0f);
794 SkISize fAtlasDimensions = {-1, -1};
795 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix();
796
797 UniformHandle fDistanceAdjustUni;
798 UniformHandle fAtlasDimensionsInvUniform;
799 UniformHandle fLocalMatrixUniform;
800 };
801
802 ///////////////////////////////////////////////////////////////////////////////
803
GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps, const GrSurfaceProxyView* views, int numViews, GrSamplerState params, DistanceAdjust distanceAdjust, uint32_t flags, const SkMatrix& localMatrix)804 GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps,
805 const GrSurfaceProxyView* views,
806 int numViews,
807 GrSamplerState params,
808 DistanceAdjust distanceAdjust,
809 uint32_t flags,
810 const SkMatrix& localMatrix)
811 : INHERITED(kGrDistanceFieldLCDTextGeoProc_ClassID)
812 , fLocalMatrix(localMatrix)
813 , fDistanceAdjust(distanceAdjust)
814 , fFlags(flags & kLCD_DistanceFieldEffectMask) {
815 SkASSERT(numViews <= kMaxTextures);
816 SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag));
817
818 if (fFlags & kPerspective_DistanceFieldEffectFlag) {
819 fInPosition = {"inPosition", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
820 } else {
821 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
822 }
823 fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
824 fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
825 caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
826 this->setVertexAttributes(&fInPosition, 3);
827
828 if (numViews) {
829 fAtlasDimensions = views[0].proxy()->dimensions();
830 }
831
832 for (int i = 0; i < numViews; ++i) {
833 const GrSurfaceProxy* proxy = views[i].proxy();
834 SkASSERT(proxy);
835 SkASSERT(proxy->dimensions() == fAtlasDimensions);
836 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
837 }
838 this->setTextureSamplerCnt(numViews);
839 }
840
addNewViews(const GrSurfaceProxyView* views, int numViews, GrSamplerState params)841 void GrDistanceFieldLCDTextGeoProc::addNewViews(const GrSurfaceProxyView* views,
842 int numViews,
843 GrSamplerState params) {
844 SkASSERT(numViews <= kMaxTextures);
845 // Just to make sure we don't try to add too many proxies
846 numViews = std::min(numViews, kMaxTextures);
847
848 if (!fTextureSamplers[0].isInitialized()) {
849 fAtlasDimensions = views[0].proxy()->dimensions();
850 }
851
852 for (int i = 0; i < numViews; ++i) {
853 const GrSurfaceProxy* proxy = views[i].proxy();
854 SkASSERT(proxy);
855 SkASSERT(proxy->dimensions() == fAtlasDimensions);
856 if (!fTextureSamplers[i].isInitialized()) {
857 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
858 }
859 }
860 this->setTextureSamplerCnt(numViews);
861 }
862
getShaderDfxInfo() const863 SkString GrDistanceFieldLCDTextGeoProc::getShaderDfxInfo() const
864 {
865 SkString format;
866 format.printf("ShaderDfx_GrDistanceFieldLCDTextGeoProc_%d_%d_%d_%d_%d", fFlags, numTextureSamplers(),
867 fLocalMatrix.isIdentity(), fLocalMatrix.isScaleTranslate(), fLocalMatrix.hasPerspective());
868 return format;
869 }
870
addToKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const871 void GrDistanceFieldLCDTextGeoProc::addToKey(const GrShaderCaps& caps,
872 GrProcessorKeyBuilder* b) const {
873 uint32_t key = 0;
874 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix);
875 key |= fFlags << 16;
876 b->add32(key);
877 b->add32(this->numTextureSamplers());
878 }
879
makeProgramImpl( const GrShaderCaps&) const880 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldLCDTextGeoProc::makeProgramImpl(
881 const GrShaderCaps&) const {
882 return std::make_unique<Impl>();
883 }
884
885 ///////////////////////////////////////////////////////////////////////////////
886
887 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc);
888
889 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData* d)890 GrGeometryProcessor* GrDistanceFieldLCDTextGeoProc::TestCreate(GrProcessorTestData* d) {
891 auto [view, ct, at] = d->randomView();
892
893 GrSamplerState::WrapMode wrapModes[2];
894 GrTest::TestWrapModes(d->fRandom, wrapModes);
895 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
896 ? GrSamplerState::Filter::kLinear
897 : GrSamplerState::Filter::kNearest);
898 DistanceAdjust wa = { 0.0f, 0.1f, -0.1f };
899 uint32_t flags = kUseLCD_DistanceFieldEffectFlag;
900 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
901 if (flags & kSimilarity_DistanceFieldEffectFlag) {
902 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
903 }
904 flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0;
905 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
906
907 return GrDistanceFieldLCDTextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(), &view,
908 1, samplerState, wa, flags, localMatrix);
909 }
910 #endif
911