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