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