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:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)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:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)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
addToKey(const GrShaderCaps & caps,GrProcessorKeyBuilder * b) const267 void GrDistanceFieldA8TextGeoProc::addToKey(const GrShaderCaps& caps,
268 GrProcessorKeyBuilder* b) const {
269 uint32_t key = 0;
270 key |= fFlags;
271 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 16;
272 b->add32(key);
273 b->add32(this->numTextureSamplers());
274 }
275
makeProgramImpl(const GrShaderCaps &) const276 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldA8TextGeoProc::makeProgramImpl(
277 const GrShaderCaps&) const {
278 return std::make_unique<Impl>();
279 }
280
281 ///////////////////////////////////////////////////////////////////////////////
282
283 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc);
284
285 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)286 GrGeometryProcessor* GrDistanceFieldA8TextGeoProc::TestCreate(GrProcessorTestData* d) {
287 auto [view, ct, at] = d->randomAlphaOnlyView();
288
289 GrSamplerState::WrapMode wrapModes[2];
290 GrTest::TestWrapModes(d->fRandom, wrapModes);
291 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
292 ? GrSamplerState::Filter::kLinear
293 : GrSamplerState::Filter::kNearest);
294
295 uint32_t flags = 0;
296 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
297 if (flags & kSimilarity_DistanceFieldEffectFlag) {
298 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
299 }
300 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
301 #ifdef SK_GAMMA_APPLY_TO_A8
302 float lum = d->fRandom->nextF();
303 #endif
304 return GrDistanceFieldA8TextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
305 &view, 1,
306 samplerState,
307 #ifdef SK_GAMMA_APPLY_TO_A8
308 lum,
309 #endif
310 flags, localMatrix);
311 }
312 #endif
313
314 ///////////////////////////////////////////////////////////////////////////////
315
316 class GrDistanceFieldPathGeoProc::Impl : public ProgramImpl {
317 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)318 void setData(const GrGLSLProgramDataManager& pdman,
319 const GrShaderCaps& shaderCaps,
320 const GrGeometryProcessor& geomProc) override {
321 const GrDistanceFieldPathGeoProc& dfpgp = geomProc.cast<GrDistanceFieldPathGeoProc>();
322
323 // We always set the matrix uniform; it's either used to transform from local to device
324 // for the output position, or from device to local for the local coord variable.
325 SetTransform(pdman, shaderCaps, fMatrixUniform, dfpgp.fMatrix, &fMatrix);
326
327 const SkISize& atlasDimensions = dfpgp.fAtlasDimensions;
328 SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
329 if (fAtlasDimensions != atlasDimensions) {
330 pdman.set2f(fAtlasDimensionsInvUniform,
331 1.0f / atlasDimensions.fWidth,
332 1.0f / atlasDimensions.fHeight);
333 fAtlasDimensions = atlasDimensions;
334 }
335 }
336
337 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)338 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
339 const GrDistanceFieldPathGeoProc& dfPathEffect =
340 args.fGeomProc.cast<GrDistanceFieldPathGeoProc>();
341
342 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
343
344 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
345 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
346 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
347
348 // emit attributes
349 varyingHandler->emitAttributes(dfPathEffect);
350
351 const char* atlasDimensionsInvName;
352 fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
353 kVertex_GrShaderFlag,
354 kFloat2_GrSLType,
355 "AtlasDimensionsInv",
356 &atlasDimensionsInvName);
357
358 GrGLSLVarying uv, texIdx, st;
359 append_index_uv_varyings(args,
360 dfPathEffect.numTextureSamplers(),
361 dfPathEffect.fInTextureCoords.name(),
362 atlasDimensionsInvName,
363 &uv,
364 &texIdx,
365 &st);
366
367 // setup pass through color
368 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
369 varyingHandler->addPassThroughAttribute(dfPathEffect.fInColor.asShaderVar(),
370 args.fOutputColor);
371
372 if (dfPathEffect.fMatrix.hasPerspective()) {
373 // Setup position (output position is transformed, local coords are pass through)
374 WriteOutputPosition(vertBuilder,
375 uniformHandler,
376 *args.fShaderCaps,
377 gpArgs,
378 dfPathEffect.fInPosition.name(),
379 dfPathEffect.fMatrix,
380 &fMatrixUniform);
381 gpArgs->fLocalCoordVar = dfPathEffect.fInPosition.asShaderVar();
382 } else {
383 // Setup position (output position is pass through, local coords are transformed)
384 WriteOutputPosition(vertBuilder, gpArgs, dfPathEffect.fInPosition.name());
385 WriteLocalCoord(vertBuilder,
386 uniformHandler,
387 *args.fShaderCaps,
388 gpArgs,
389 dfPathEffect.fInPosition.asShaderVar(),
390 dfPathEffect.fMatrix,
391 &fMatrixUniform);
392 }
393
394 // Use highp to work around aliasing issues
395 fragBuilder->codeAppendf("float2 uv = %s;", uv.fsIn());
396 fragBuilder->codeAppend("half4 texColor;");
397 append_multitexture_lookup(args, dfPathEffect.numTextureSamplers(), texIdx, "uv",
398 "texColor");
399
400 fragBuilder->codeAppend("half distance = "
401 SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
402
403 fragBuilder->codeAppend("half afwidth;");
404 bool isUniformScale = (dfPathEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
405 kUniformScale_DistanceFieldEffectMask;
406 bool isSimilarity = SkToBool(dfPathEffect.fFlags & kSimilarity_DistanceFieldEffectFlag );
407 bool isGammaCorrect = SkToBool(dfPathEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
408 if (isUniformScale) {
409 // For uniform scale, we adjust for the effect of the transformation on the distance
410 // by using the length of the gradient of the t coordinate in the y direction.
411 // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
412
413 // this gives us a smooth step across approximately one fragment
414 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
415 fragBuilder->codeAppendf(
416 "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdy(%s.y)));", st.fsIn());
417 } else {
418 fragBuilder->codeAppendf(
419 "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdx(%s.x)));", st.fsIn());
420 }
421 } else if (isSimilarity) {
422 // For similarity transform, we adjust the effect of the transformation on the distance
423 // by using the length of the gradient of the texture coordinates. We use st coordinates
424 // to ensure we're mapping 1:1 from texel space to pixel space.
425
426 // this gives us a smooth step across approximately one fragment
427 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
428 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdy(%s)));", st.fsIn());
429 } else {
430 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdx(%s)));", st.fsIn());
431 }
432 fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
433 } else {
434 // For general transforms, to determine the amount of correction we multiply a unit
435 // vector pointing along the SDF gradient direction by the Jacobian of the st coords
436 // (which is the inverse transform for this fragment) and take the length of the result.
437 fragBuilder->codeAppend("half2 dist_grad = half2(dFdx(distance), "
438 "dFdy(distance));");
439 // the length of the gradient may be 0, so we need to check for this
440 // this also compensates for the Adreno, which likes to drop tiles on division by 0
441 fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);");
442 fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
443 fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);");
444 fragBuilder->codeAppend("} else {");
445 fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));");
446 fragBuilder->codeAppend("}");
447
448 fragBuilder->codeAppendf("half2 Jdx = half2(dFdx(%s));", st.fsIn());
449 fragBuilder->codeAppendf("half2 Jdy = half2(dFdy(%s));", st.fsIn());
450 fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
451 fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
452
453 // this gives us a smooth step across approximately one fragment
454 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
455 }
456 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
457 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
458 // mapped linearly to coverage, so use a linear step:
459 if (isGammaCorrect) {
460 fragBuilder->codeAppend(
461 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
462 } else {
463 fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
464 }
465
466 fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
467 }
468
469 SkMatrix fMatrix; // view matrix if perspective, local matrix otherwise
470 UniformHandle fMatrixUniform;
471
472 SkISize fAtlasDimensions;
473 UniformHandle fAtlasDimensionsInvUniform;
474
475 using INHERITED = ProgramImpl;
476 };
477
478 ///////////////////////////////////////////////////////////////////////////////
479
GrDistanceFieldPathGeoProc(const GrShaderCaps & caps,const SkMatrix & matrix,bool wideColor,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,uint32_t flags)480 GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
481 const SkMatrix& matrix,
482 bool wideColor,
483 const GrSurfaceProxyView* views,
484 int numViews,
485 GrSamplerState params,
486 uint32_t flags)
487 : INHERITED(kGrDistanceFieldPathGeoProc_ClassID)
488 , fMatrix(matrix)
489 , fFlags(flags & kNonLCD_DistanceFieldEffectMask) {
490 SkASSERT(numViews <= kMaxTextures);
491 SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
492
493 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
494 fInColor = MakeColorAttribute("inColor", wideColor);
495 fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
496 caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
497 this->setVertexAttributes(&fInPosition, 3);
498
499 if (numViews) {
500 fAtlasDimensions = views[0].proxy()->dimensions();
501 }
502
503 for (int i = 0; i < numViews; ++i) {
504 const GrSurfaceProxy* proxy = views[i].proxy();
505 SkASSERT(proxy);
506 SkASSERT(proxy->dimensions() == fAtlasDimensions);
507 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
508 }
509 this->setTextureSamplerCnt(numViews);
510 }
511
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)512 void GrDistanceFieldPathGeoProc::addNewViews(const GrSurfaceProxyView* views,
513 int numViews,
514 GrSamplerState params) {
515 SkASSERT(numViews <= kMaxTextures);
516 // Just to make sure we don't try to add too many proxies
517 numViews = std::min(numViews, kMaxTextures);
518
519 if (!fTextureSamplers[0].isInitialized()) {
520 fAtlasDimensions = views[0].proxy()->dimensions();
521 }
522
523 for (int i = 0; i < numViews; ++i) {
524 const GrSurfaceProxy* proxy = views[i].proxy();
525 SkASSERT(proxy);
526 SkASSERT(proxy->dimensions() == fAtlasDimensions);
527 if (!fTextureSamplers[i].isInitialized()) {
528 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
529 }
530 }
531 this->setTextureSamplerCnt(numViews);
532 }
533
addToKey(const GrShaderCaps & caps,GrProcessorKeyBuilder * b) const534 void GrDistanceFieldPathGeoProc::addToKey(const GrShaderCaps& caps,
535 GrProcessorKeyBuilder* b) const {
536 uint32_t key = fFlags;
537 key |= ProgramImpl::ComputeMatrixKey(caps, fMatrix) << 16;
538 key |= fMatrix.hasPerspective() << (16 + ProgramImpl::kMatrixKeyBits);
539 b->add32(key);
540 b->add32(this->numTextureSamplers());
541 }
542
makeProgramImpl(const GrShaderCaps &) const543 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldPathGeoProc::makeProgramImpl(
544 const GrShaderCaps&) const {
545 return std::make_unique<Impl>();
546 }
547
548 ///////////////////////////////////////////////////////////////////////////////
549
550 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc);
551
552 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)553 GrGeometryProcessor* GrDistanceFieldPathGeoProc::TestCreate(GrProcessorTestData* d) {
554 auto [view, ct, at] = d->randomAlphaOnlyView();
555
556 GrSamplerState::WrapMode wrapModes[2];
557 GrTest::TestWrapModes(d->fRandom, wrapModes);
558 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
559 ? GrSamplerState::Filter::kLinear
560 : GrSamplerState::Filter::kNearest);
561
562 uint32_t flags = 0;
563 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
564 if (flags & kSimilarity_DistanceFieldEffectFlag) {
565 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
566 }
567 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
568 bool wideColor = d->fRandom->nextBool();
569 return GrDistanceFieldPathGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
570 localMatrix,
571 wideColor,
572 &view, 1,
573 samplerState,
574 flags);
575 }
576 #endif
577
578 ///////////////////////////////////////////////////////////////////////////////
579
580 class GrDistanceFieldLCDTextGeoProc::Impl : public ProgramImpl {
581 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)582 void setData(const GrGLSLProgramDataManager& pdman,
583 const GrShaderCaps& shaderCaps,
584 const GrGeometryProcessor& geomProc) override {
585 SkASSERT(fDistanceAdjustUni.isValid());
586
587 const GrDistanceFieldLCDTextGeoProc& dflcd = geomProc.cast<GrDistanceFieldLCDTextGeoProc>();
588 GrDistanceFieldLCDTextGeoProc::DistanceAdjust wa = dflcd.fDistanceAdjust;
589 if (wa != fDistanceAdjust) {
590 pdman.set3f(fDistanceAdjustUni, wa.fR, wa.fG, wa.fB);
591 fDistanceAdjust = wa;
592 }
593
594 const SkISize& atlasDimensions = dflcd.fAtlasDimensions;
595 SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
596 if (fAtlasDimensions != atlasDimensions) {
597 pdman.set2f(fAtlasDimensionsInvUniform,
598 1.0f / atlasDimensions.fWidth,
599 1.0f / atlasDimensions.fHeight);
600 fAtlasDimensions = atlasDimensions;
601 }
602 SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dflcd.fLocalMatrix, &fLocalMatrix);
603 }
604
605 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)606 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
607 const GrDistanceFieldLCDTextGeoProc& dfTexEffect =
608 args.fGeomProc.cast<GrDistanceFieldLCDTextGeoProc>();
609
610 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
611 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
612 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
613
614 // emit attributes
615 varyingHandler->emitAttributes(dfTexEffect);
616
617 const char* atlasDimensionsInvName;
618 fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
619 kVertex_GrShaderFlag,
620 kFloat2_GrSLType,
621 "AtlasDimensionsInv",
622 &atlasDimensionsInvName);
623
624 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
625
626 // setup pass through color
627 fragBuilder->codeAppendf("half4 %s;\n", args.fOutputColor);
628 varyingHandler->addPassThroughAttribute(dfTexEffect.fInColor.asShaderVar(),
629 args.fOutputColor);
630
631 // Setup position
632 gpArgs->fPositionVar = dfTexEffect.fInPosition.asShaderVar();
633 WriteLocalCoord(vertBuilder,
634 uniformHandler,
635 *args.fShaderCaps,
636 gpArgs,
637 dfTexEffect.fInPosition.asShaderVar(),
638 dfTexEffect.fLocalMatrix,
639 &fLocalMatrixUniform);
640
641 // set up varyings
642 GrGLSLVarying uv, texIdx, st;
643 append_index_uv_varyings(args,
644 dfTexEffect.numTextureSamplers(),
645 dfTexEffect.fInTextureCoords.name(),
646 atlasDimensionsInvName,
647 &uv,
648 &texIdx,
649 &st);
650
651 GrGLSLVarying delta(kFloat_GrSLType);
652 varyingHandler->addVarying("Delta", &delta);
653 if (dfTexEffect.fFlags & kBGR_DistanceFieldEffectFlag) {
654 vertBuilder->codeAppendf("%s = -%s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
655 } else {
656 vertBuilder->codeAppendf("%s = %s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
657 }
658
659 // add frag shader code
660 bool isUniformScale = (dfTexEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
661 kUniformScale_DistanceFieldEffectMask;
662 bool isSimilarity = SkToBool(dfTexEffect.fFlags & kSimilarity_DistanceFieldEffectFlag );
663 bool isGammaCorrect = SkToBool(dfTexEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
664
665 // create LCD offset adjusted by inverse of transform
666 // Use highp to work around aliasing issues
667 fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn());
668
669 if (isUniformScale) {
670 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
671 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdy(%s.y)));", st.fsIn());
672 } else {
673 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdx(%s.x)));", st.fsIn());
674 }
675 fragBuilder->codeAppendf("half2 offset = half2(half(st_grad_len*%s), 0.0);",
676 delta.fsIn());
677 } else if (isSimilarity) {
678 // For a similarity matrix with rotation, the gradient will not be aligned
679 // with the texel coordinate axes, so we need to calculate it.
680 if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
681 // We use dFdy instead and rotate -90 degrees to get the gradient in the x
682 // direction.
683 fragBuilder->codeAppendf("half2 st_grad = half2(dFdy(%s));", st.fsIn());
684 fragBuilder->codeAppendf("half2 offset = half2(%s*float2(st_grad.y, -st_grad.x));",
685 delta.fsIn());
686 } else {
687 fragBuilder->codeAppendf("half2 st_grad = half2(dFdx(%s));", st.fsIn());
688 fragBuilder->codeAppendf("half2 offset = half(%s)*st_grad;", delta.fsIn());
689 }
690 fragBuilder->codeAppend("half st_grad_len = length(st_grad);");
691 } else {
692 fragBuilder->codeAppendf("half2 st = half2(%s);\n", st.fsIn());
693
694 fragBuilder->codeAppend("half2 Jdx = half2(dFdx(st));");
695 fragBuilder->codeAppend("half2 Jdy = half2(dFdy(st));");
696 fragBuilder->codeAppendf("half2 offset = half2(half(%s))*Jdx;", delta.fsIn());
697 }
698
699 // sample the texture by index
700 fragBuilder->codeAppend("half4 texColor;");
701 append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
702 texIdx, "uv", "texColor");
703
704 // green is distance to uv center
705 fragBuilder->codeAppend("half3 distance;");
706 fragBuilder->codeAppend("distance.y = texColor.r;");
707 // red is distance to left offset
708 fragBuilder->codeAppend("half2 uv_adjusted = half2(uv) - offset;");
709 append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
710 texIdx, "uv_adjusted", "texColor");
711 fragBuilder->codeAppend("distance.x = texColor.r;");
712 // blue is distance to right offset
713 fragBuilder->codeAppend("uv_adjusted = half2(uv) + offset;");
714 append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
715 texIdx, "uv_adjusted", "texColor");
716 fragBuilder->codeAppend("distance.z = texColor.r;");
717
718 fragBuilder->codeAppend("distance = "
719 "half3(" SK_DistanceFieldMultiplier ")*(distance - half3(" SK_DistanceFieldThreshold"));");
720
721 // adjust width based on gamma
722 const char* distanceAdjustUniName = nullptr;
723 fDistanceAdjustUni = uniformHandler->addUniform(nullptr, kFragment_GrShaderFlag,
724 kHalf3_GrSLType, "DistanceAdjust",
725 &distanceAdjustUniName);
726 fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName);
727
728 // To be strictly correct, we should compute the anti-aliasing factor separately
729 // for each color component. However, this is only important when using perspective
730 // transformations, and even then using a single factor seems like a reasonable
731 // trade-off between quality and speed.
732 fragBuilder->codeAppend("half afwidth;");
733 if (isSimilarity) {
734 // For similarity transform (uniform scale-only is a subset of this), we adjust for the
735 // effect of the transformation on the distance by using the length of the gradient of
736 // the texture coordinates. We use st coordinates to ensure we're mapping 1:1 from texel
737 // space to pixel space.
738
739 // this gives us a smooth step across approximately one fragment
740 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*st_grad_len;");
741 } else {
742 // For general transforms, to determine the amount of correction we multiply a unit
743 // vector pointing along the SDF gradient direction by the Jacobian of the st coords
744 // (which is the inverse transform for this fragment) and take the length of the result.
745 fragBuilder->codeAppend("half2 dist_grad = half2(half(dFdx(distance.r)), "
746 "half(dFdy(distance.r)));");
747 // the length of the gradient may be 0, so we need to check for this
748 // this also compensates for the Adreno, which likes to drop tiles on division by 0
749 fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);");
750 fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
751 fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);");
752 fragBuilder->codeAppend("} else {");
753 fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));");
754 fragBuilder->codeAppend("}");
755 fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
756 fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
757
758 // this gives us a smooth step across approximately one fragment
759 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
760 }
761
762 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
763 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
764 // mapped linearly to coverage, so use a linear step:
765 if (isGammaCorrect) {
766 fragBuilder->codeAppendf("half4 %s = "
767 "half4(saturate((distance + half3(afwidth)) / half3(2.0 * afwidth)), 1.0);",
768 args.fOutputCoverage);
769 } else {
770 fragBuilder->codeAppendf(
771 "half4 %s = half4(smoothstep(half3(-afwidth), half3(afwidth), distance), 1.0);",
772 args.fOutputCoverage);
773 }
774 }
775
776 private:
777 DistanceAdjust fDistanceAdjust = DistanceAdjust::Make(1.0f, 1.0f, 1.0f);
778 SkISize fAtlasDimensions = {-1, -1};
779 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix();
780
781 UniformHandle fDistanceAdjustUni;
782 UniformHandle fAtlasDimensionsInvUniform;
783 UniformHandle fLocalMatrixUniform;
784 };
785
786 ///////////////////////////////////////////////////////////////////////////////
787
GrDistanceFieldLCDTextGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,DistanceAdjust distanceAdjust,uint32_t flags,const SkMatrix & localMatrix)788 GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps,
789 const GrSurfaceProxyView* views,
790 int numViews,
791 GrSamplerState params,
792 DistanceAdjust distanceAdjust,
793 uint32_t flags,
794 const SkMatrix& localMatrix)
795 : INHERITED(kGrDistanceFieldLCDTextGeoProc_ClassID)
796 , fLocalMatrix(localMatrix)
797 , fDistanceAdjust(distanceAdjust)
798 , fFlags(flags & kLCD_DistanceFieldEffectMask) {
799 SkASSERT(numViews <= kMaxTextures);
800 SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag));
801
802 if (fFlags & kPerspective_DistanceFieldEffectFlag) {
803 fInPosition = {"inPosition", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
804 } else {
805 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
806 }
807 fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
808 fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
809 caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
810 this->setVertexAttributes(&fInPosition, 3);
811
812 if (numViews) {
813 fAtlasDimensions = views[0].proxy()->dimensions();
814 }
815
816 for (int i = 0; i < numViews; ++i) {
817 const GrSurfaceProxy* proxy = views[i].proxy();
818 SkASSERT(proxy);
819 SkASSERT(proxy->dimensions() == fAtlasDimensions);
820 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
821 }
822 this->setTextureSamplerCnt(numViews);
823 }
824
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)825 void GrDistanceFieldLCDTextGeoProc::addNewViews(const GrSurfaceProxyView* views,
826 int numViews,
827 GrSamplerState params) {
828 SkASSERT(numViews <= kMaxTextures);
829 // Just to make sure we don't try to add too many proxies
830 numViews = std::min(numViews, kMaxTextures);
831
832 if (!fTextureSamplers[0].isInitialized()) {
833 fAtlasDimensions = views[0].proxy()->dimensions();
834 }
835
836 for (int i = 0; i < numViews; ++i) {
837 const GrSurfaceProxy* proxy = views[i].proxy();
838 SkASSERT(proxy);
839 SkASSERT(proxy->dimensions() == fAtlasDimensions);
840 if (!fTextureSamplers[i].isInitialized()) {
841 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
842 }
843 }
844 this->setTextureSamplerCnt(numViews);
845 }
846
addToKey(const GrShaderCaps & caps,GrProcessorKeyBuilder * b) const847 void GrDistanceFieldLCDTextGeoProc::addToKey(const GrShaderCaps& caps,
848 GrProcessorKeyBuilder* b) const {
849 uint32_t key = 0;
850 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix);
851 key |= fFlags << 16;
852 b->add32(key);
853 b->add32(this->numTextureSamplers());
854 }
855
makeProgramImpl(const GrShaderCaps &) const856 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldLCDTextGeoProc::makeProgramImpl(
857 const GrShaderCaps&) const {
858 return std::make_unique<Impl>();
859 }
860
861 ///////////////////////////////////////////////////////////////////////////////
862
863 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc);
864
865 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)866 GrGeometryProcessor* GrDistanceFieldLCDTextGeoProc::TestCreate(GrProcessorTestData* d) {
867 auto [view, ct, at] = d->randomView();
868
869 GrSamplerState::WrapMode wrapModes[2];
870 GrTest::TestWrapModes(d->fRandom, wrapModes);
871 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
872 ? GrSamplerState::Filter::kLinear
873 : GrSamplerState::Filter::kNearest);
874 DistanceAdjust wa = { 0.0f, 0.1f, -0.1f };
875 uint32_t flags = kUseLCD_DistanceFieldEffectFlag;
876 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
877 if (flags & kSimilarity_DistanceFieldEffectFlag) {
878 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
879 }
880 flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0;
881 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
882
883 return GrDistanceFieldLCDTextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(), &view,
884 1, samplerState, wa, flags, localMatrix);
885 }
886 #endif
887