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("half4 jacobian = half4(dFdx(%s), dFdy(%s));",
180 st.fsIn(), st.fsIn());
181 fragBuilder->codeAppend("half2 grad = half2(dot(dist_grad, jacobian.xz),"
182 "dot(dist_grad, jacobian.yw));");
183
184 // this gives us a smooth step across approximately one fragment
185 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
186 }
187
188 if (isAliased) {
189 fragBuilder->codeAppend("half val = distance > 0 ? 1.0 : 0.0;");
190 } else if (isGammaCorrect) {
191 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
192 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want
193 // distance mapped linearly to coverage, so use a linear step:
194 fragBuilder->codeAppend(
195 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
196 } else {
197 fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
198 }
199
200 fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
201 }
202
203 private:
204 #ifdef SK_GAMMA_APPLY_TO_A8
205 float fDistanceAdjust = -1.f;
206 #endif
207 SkISize fAtlasDimensions = {-1, -1};
208 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix();
209
210 UniformHandle fDistanceAdjustUni;
211 UniformHandle fAtlasDimensionsInvUniform;
212 UniformHandle fLocalMatrixUniform;
213
214 using INHERITED = ProgramImpl;
215 };
216
217 ///////////////////////////////////////////////////////////////////////////////
218
GrDistanceFieldA8TextGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,float distanceAdjust,uint32_t flags,const SkMatrix & localMatrix)219 GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(const GrShaderCaps& caps,
220 const GrSurfaceProxyView* views,
221 int numViews,
222 GrSamplerState params,
223 #ifdef SK_GAMMA_APPLY_TO_A8
224 float distanceAdjust,
225 #endif
226 uint32_t flags,
227 const SkMatrix& localMatrix)
228 : INHERITED(kGrDistanceFieldA8TextGeoProc_ClassID)
229 , fLocalMatrix(localMatrix)
230 , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
231 #ifdef SK_GAMMA_APPLY_TO_A8
232 , fDistanceAdjust(distanceAdjust)
233 #endif
234 {
235 SkASSERT(numViews <= kMaxTextures);
236 SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
237
238 if (flags & kPerspective_DistanceFieldEffectFlag) {
239 fInPosition = {"inPosition", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
240 } else {
241 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
242 }
243 fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, SkSLType::kHalf4 };
244 fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
245 caps.fIntegerSupport ? SkSLType::kUShort2 : SkSLType::kFloat2};
246 this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
247
248 if (numViews) {
249 fAtlasDimensions = views[0].proxy()->dimensions();
250 }
251 for (int i = 0; i < numViews; ++i) {
252 const GrSurfaceProxy* proxy = views[i].proxy();
253 SkASSERT(proxy);
254 SkASSERT(proxy->dimensions() == fAtlasDimensions);
255 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
256 }
257 this->setTextureSamplerCnt(numViews);
258 }
259
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)260 void GrDistanceFieldA8TextGeoProc::addNewViews(const GrSurfaceProxyView* views,
261 int numViews,
262 GrSamplerState params) {
263 SkASSERT(numViews <= kMaxTextures);
264 // Just to make sure we don't try to add too many proxies
265 numViews = std::min(numViews, kMaxTextures);
266
267 if (!fTextureSamplers[0].isInitialized()) {
268 fAtlasDimensions = views[0].proxy()->dimensions();
269 }
270
271 for (int i = 0; i < numViews; ++i) {
272 const GrSurfaceProxy* proxy = views[i].proxy();
273 SkASSERT(proxy);
274 SkASSERT(proxy->dimensions() == fAtlasDimensions);
275 if (!fTextureSamplers[i].isInitialized()) {
276 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
277 }
278 }
279 this->setTextureSamplerCnt(numViews);
280 }
281
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const282 void GrDistanceFieldA8TextGeoProc::addToKey(const GrShaderCaps& caps,
283 skgpu::KeyBuilder* b) const {
284 uint32_t key = 0;
285 key |= fFlags;
286 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 16;
287 b->add32(key);
288 b->add32(this->numTextureSamplers());
289 }
290
makeProgramImpl(const GrShaderCaps &) const291 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldA8TextGeoProc::makeProgramImpl(
292 const GrShaderCaps&) const {
293 return std::make_unique<Impl>();
294 }
295
296 ///////////////////////////////////////////////////////////////////////////////
297
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc)298 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc)
299
300 #if defined(GR_TEST_UTILS)
301 GrGeometryProcessor* GrDistanceFieldA8TextGeoProc::TestCreate(GrProcessorTestData* d) {
302 auto [view, ct, at] = d->randomAlphaOnlyView();
303
304 GrSamplerState::WrapMode wrapModes[2];
305 GrTest::TestWrapModes(d->fRandom, wrapModes);
306 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
307 ? GrSamplerState::Filter::kLinear
308 : GrSamplerState::Filter::kNearest);
309
310 uint32_t flags = 0;
311 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
312 if (flags & kSimilarity_DistanceFieldEffectFlag) {
313 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
314 }
315 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
316 #ifdef SK_GAMMA_APPLY_TO_A8
317 float lum = d->fRandom->nextF();
318 #endif
319 return GrDistanceFieldA8TextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
320 &view, 1,
321 samplerState,
322 #ifdef SK_GAMMA_APPLY_TO_A8
323 lum,
324 #endif
325 flags, localMatrix);
326 }
327 #endif
328
329 ///////////////////////////////////////////////////////////////////////////////
330
331 class GrDistanceFieldPathGeoProc::Impl : public ProgramImpl {
332 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)333 void setData(const GrGLSLProgramDataManager& pdman,
334 const GrShaderCaps& shaderCaps,
335 const GrGeometryProcessor& geomProc) override {
336 const GrDistanceFieldPathGeoProc& dfpgp = geomProc.cast<GrDistanceFieldPathGeoProc>();
337
338 // We always set the matrix uniform. It's used to transform from from device to local
339 // for the local coord variable.
340 SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dfpgp.fLocalMatrix, &fLocalMatrix);
341
342 const SkISize& atlasDimensions = dfpgp.fAtlasDimensions;
343 SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
344 if (fAtlasDimensions != atlasDimensions) {
345 pdman.set2f(fAtlasDimensionsInvUniform,
346 1.0f / atlasDimensions.fWidth,
347 1.0f / atlasDimensions.fHeight);
348 fAtlasDimensions = atlasDimensions;
349 }
350 }
351
352 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)353 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
354 const GrDistanceFieldPathGeoProc& dfPathEffect =
355 args.fGeomProc.cast<GrDistanceFieldPathGeoProc>();
356
357 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
358
359 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
360 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
361 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
362
363 // emit attributes
364 varyingHandler->emitAttributes(dfPathEffect);
365
366 const char* atlasDimensionsInvName;
367 fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
368 kVertex_GrShaderFlag,
369 SkSLType::kFloat2,
370 "AtlasDimensionsInv",
371 &atlasDimensionsInvName);
372
373 GrGLSLVarying uv, texIdx, st;
374 append_index_uv_varyings(args,
375 dfPathEffect.numTextureSamplers(),
376 dfPathEffect.fInTextureCoords.name(),
377 atlasDimensionsInvName,
378 &uv,
379 &texIdx,
380 &st);
381
382 // setup pass through color
383 fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
384 varyingHandler->addPassThroughAttribute(dfPathEffect.fInColor.asShaderVar(),
385 args.fOutputColor);
386
387 // Setup position (output position is pass through, local coords are transformed)
388 gpArgs->fPositionVar = dfPathEffect.fInPosition.asShaderVar();
389 WriteLocalCoord(vertBuilder,
390 uniformHandler,
391 *args.fShaderCaps,
392 gpArgs,
393 gpArgs->fPositionVar,
394 dfPathEffect.fLocalMatrix,
395 &fLocalMatrixUniform);
396
397 // Use highp to work around aliasing issues
398 fragBuilder->codeAppendf("float2 uv = %s;", uv.fsIn());
399 fragBuilder->codeAppend("half4 texColor;");
400 append_multitexture_lookup(args, dfPathEffect.numTextureSamplers(), texIdx, "uv",
401 "texColor");
402
403 fragBuilder->codeAppend("half distance = "
404 SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
405
406 fragBuilder->codeAppend("half afwidth;");
407 bool isUniformScale = (dfPathEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
408 kUniformScale_DistanceFieldEffectMask;
409 bool isSimilarity = SkToBool(dfPathEffect.fFlags & kSimilarity_DistanceFieldEffectFlag );
410 bool isGammaCorrect = SkToBool(dfPathEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
411 if (isUniformScale) {
412 // For uniform scale, we adjust for the effect of the transformation on the distance
413 // by using the length of the gradient of the t coordinate in the y direction.
414 // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
415
416 // this gives us a smooth step across approximately one fragment
417 if (args.fShaderCaps->fAvoidDfDxForGradientsWhenPossible) {
418 fragBuilder->codeAppendf(
419 "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdy(%s.y)));", st.fsIn());
420 } else {
421 fragBuilder->codeAppendf(
422 "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdx(%s.x)));", st.fsIn());
423 }
424 } else if (isSimilarity) {
425 // For similarity transform, we adjust the effect of the transformation on the distance
426 // by using the length of the gradient of the texture coordinates. We use st coordinates
427 // to ensure we're mapping 1:1 from texel space to pixel space.
428
429 // this gives us a smooth step across approximately one fragment
430 if (args.fShaderCaps->fAvoidDfDxForGradientsWhenPossible) {
431 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdy(%s)));", st.fsIn());
432 } else {
433 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdx(%s)));", st.fsIn());
434 }
435 fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
436 } else {
437 // For general transforms, to determine the amount of correction we multiply a unit
438 // vector pointing along the SDF gradient direction by the Jacobian of the st coords
439 // (which is the inverse transform for this fragment) and take the length of the result.
440 fragBuilder->codeAppend("half2 dist_grad = half2(dFdx(distance), "
441 "dFdy(distance));");
442 // the length of the gradient may be 0, so we need to check for this
443 // this also compensates for the Adreno, which likes to drop tiles on division by 0
444 fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);"
445 "if (dg_len2 < 0.0001) {"
446 "dist_grad = half2(0.7071, 0.7071);"
447 "} else {"
448 "dist_grad = dist_grad*half(inversesqrt(dg_len2));"
449 "}");
450
451 fragBuilder->codeAppendf("half4 jacobian = half4(dFdx(%s), dFdy(%s));",
452 st.fsIn(), st.fsIn());
453 fragBuilder->codeAppend("half2 grad = half2(dot(dist_grad, jacobian.xz),"
454 "dot(dist_grad, jacobian.yw));");
455
456 // this gives us a smooth step across approximately one fragment
457 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
458 }
459 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
460 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
461 // mapped linearly to coverage, so use a linear step:
462 if (isGammaCorrect) {
463 fragBuilder->codeAppend(
464 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
465 } else {
466 fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
467 }
468
469 fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
470 }
471
472 SkMatrix fLocalMatrix;
473 UniformHandle fLocalMatrixUniform;
474
475 SkISize fAtlasDimensions;
476 UniformHandle fAtlasDimensionsInvUniform;
477
478 using INHERITED = ProgramImpl;
479 };
480
481 ///////////////////////////////////////////////////////////////////////////////
482
GrDistanceFieldPathGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,const SkMatrix & localMatrix,uint32_t flags)483 GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
484 const GrSurfaceProxyView* views,
485 int numViews,
486 GrSamplerState params,
487 const SkMatrix& localMatrix,
488 uint32_t flags)
489 : INHERITED(kGrDistanceFieldPathGeoProc_ClassID)
490 , fLocalMatrix(localMatrix)
491 , fFlags(flags & kPath_DistanceFieldEffectMask) {
492 SkASSERT(numViews <= kMaxTextures);
493 SkASSERT(!(flags & ~kPath_DistanceFieldEffectMask));
494
495 fInPosition = {"inPosition", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
496 fInColor = MakeColorAttribute("inColor", SkToBool(flags & kWideColor_DistanceFieldEffectFlag));
497 fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
498 caps.fIntegerSupport ? SkSLType::kUShort2 : SkSLType::kFloat2};
499 this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
500
501 if (numViews) {
502 fAtlasDimensions = views[0].proxy()->dimensions();
503 }
504
505 for (int i = 0; i < numViews; ++i) {
506 const GrSurfaceProxy* proxy = views[i].proxy();
507 SkASSERT(proxy);
508 SkASSERT(proxy->dimensions() == fAtlasDimensions);
509 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
510 }
511 this->setTextureSamplerCnt(numViews);
512 }
513
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)514 void GrDistanceFieldPathGeoProc::addNewViews(const GrSurfaceProxyView* views,
515 int numViews,
516 GrSamplerState params) {
517 SkASSERT(numViews <= kMaxTextures);
518 // Just to make sure we don't try to add too many proxies
519 numViews = std::min(numViews, kMaxTextures);
520
521 if (!fTextureSamplers[0].isInitialized()) {
522 fAtlasDimensions = views[0].proxy()->dimensions();
523 }
524
525 for (int i = 0; i < numViews; ++i) {
526 const GrSurfaceProxy* proxy = views[i].proxy();
527 SkASSERT(proxy);
528 SkASSERT(proxy->dimensions() == fAtlasDimensions);
529 if (!fTextureSamplers[i].isInitialized()) {
530 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
531 }
532 }
533 this->setTextureSamplerCnt(numViews);
534 }
535
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const536 void GrDistanceFieldPathGeoProc::addToKey(const GrShaderCaps& caps,
537 skgpu::KeyBuilder* b) const {
538 uint32_t key = fFlags;
539 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 16;
540 key |= fLocalMatrix.hasPerspective() << (16 + ProgramImpl::kMatrixKeyBits);
541 b->add32(key);
542 b->add32(this->numTextureSamplers());
543 }
544
makeProgramImpl(const GrShaderCaps &) const545 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldPathGeoProc::makeProgramImpl(
546 const GrShaderCaps&) const {
547 return std::make_unique<Impl>();
548 }
549
550 ///////////////////////////////////////////////////////////////////////////////
551
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc)552 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc)
553
554 #if defined(GR_TEST_UTILS)
555 GrGeometryProcessor* GrDistanceFieldPathGeoProc::TestCreate(GrProcessorTestData* d) {
556 auto [view, ct, at] = d->randomAlphaOnlyView();
557
558 GrSamplerState::WrapMode wrapModes[2];
559 GrTest::TestWrapModes(d->fRandom, wrapModes);
560 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
561 ? GrSamplerState::Filter::kLinear
562 : GrSamplerState::Filter::kNearest);
563
564 uint32_t flags = 0;
565 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
566 if (flags & kSimilarity_DistanceFieldEffectFlag) {
567 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
568 }
569 flags |= d->fRandom->nextBool() ? kWideColor_DistanceFieldEffectFlag : 0;
570 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
571 return GrDistanceFieldPathGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
572 &view, 1,
573 samplerState,
574 localMatrix,
575 flags);
576 }
577 #endif
578
579
580 ///////////////////////////////////////////////////////////////////////////////
581
582 class GrDistanceFieldLCDTextGeoProc::Impl : public ProgramImpl {
583 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)584 void setData(const GrGLSLProgramDataManager& pdman,
585 const GrShaderCaps& shaderCaps,
586 const GrGeometryProcessor& geomProc) override {
587 SkASSERT(fDistanceAdjustUni.isValid());
588
589 const GrDistanceFieldLCDTextGeoProc& dflcd = geomProc.cast<GrDistanceFieldLCDTextGeoProc>();
590 GrDistanceFieldLCDTextGeoProc::DistanceAdjust wa = dflcd.fDistanceAdjust;
591 if (wa != fDistanceAdjust) {
592 pdman.set3f(fDistanceAdjustUni, wa.fR, wa.fG, wa.fB);
593 fDistanceAdjust = wa;
594 }
595
596 const SkISize& atlasDimensions = dflcd.fAtlasDimensions;
597 SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
598 if (fAtlasDimensions != atlasDimensions) {
599 pdman.set2f(fAtlasDimensionsInvUniform,
600 1.0f / atlasDimensions.fWidth,
601 1.0f / atlasDimensions.fHeight);
602 fAtlasDimensions = atlasDimensions;
603 }
604 SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dflcd.fLocalMatrix, &fLocalMatrix);
605 }
606
607 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)608 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
609 const GrDistanceFieldLCDTextGeoProc& dfTexEffect =
610 args.fGeomProc.cast<GrDistanceFieldLCDTextGeoProc>();
611
612 GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
613 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
614 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
615
616 // emit attributes
617 varyingHandler->emitAttributes(dfTexEffect);
618
619 const char* atlasDimensionsInvName;
620 fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
621 kVertex_GrShaderFlag,
622 SkSLType::kFloat2,
623 "AtlasDimensionsInv",
624 &atlasDimensionsInvName);
625
626 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
627
628 // setup pass through color
629 fragBuilder->codeAppendf("half4 %s;\n", args.fOutputColor);
630 varyingHandler->addPassThroughAttribute(dfTexEffect.fInColor.asShaderVar(),
631 args.fOutputColor);
632
633 // Setup position
634 gpArgs->fPositionVar = dfTexEffect.fInPosition.asShaderVar();
635 WriteLocalCoord(vertBuilder,
636 uniformHandler,
637 *args.fShaderCaps,
638 gpArgs,
639 dfTexEffect.fInPosition.asShaderVar(),
640 dfTexEffect.fLocalMatrix,
641 &fLocalMatrixUniform);
642
643 // set up varyings
644 GrGLSLVarying uv, texIdx, st;
645 append_index_uv_varyings(args,
646 dfTexEffect.numTextureSamplers(),
647 dfTexEffect.fInTextureCoords.name(),
648 atlasDimensionsInvName,
649 &uv,
650 &texIdx,
651 &st);
652
653 GrGLSLVarying delta(SkSLType::kFloat);
654 varyingHandler->addVarying("Delta", &delta);
655 if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
656 if (dfTexEffect.fFlags & kBGR_DistanceFieldEffectFlag) {
657 vertBuilder->codeAppendf("%s = -%s.y/3.0;", delta.vsOut(), atlasDimensionsInvName);
658 } else {
659 vertBuilder->codeAppendf("%s = %s.y/3.0;", delta.vsOut(), atlasDimensionsInvName);
660 }
661 } else {
662 if (dfTexEffect.fFlags & kBGR_DistanceFieldEffectFlag) {
663 vertBuilder->codeAppendf("%s = -%s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
664 } else {
665 vertBuilder->codeAppendf("%s = %s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
666 }
667 }
668
669 // add frag shader code
670 bool isUniformScale = (dfTexEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
671 kUniformScale_DistanceFieldEffectMask;
672 bool isSimilarity = SkToBool(dfTexEffect.fFlags & kSimilarity_DistanceFieldEffectFlag );
673 bool isGammaCorrect = SkToBool(dfTexEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
674
675 // create LCD offset adjusted by inverse of transform
676 // Use highp to work around aliasing issues
677 fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn());
678
679 if (isUniformScale) {
680 if (args.fShaderCaps->fAvoidDfDxForGradientsWhenPossible) {
681 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdy(%s.y)));", st.fsIn());
682 } else {
683 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdx(%s.x)));", st.fsIn());
684 }
685 if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
686 fragBuilder->codeAppendf("half2 offset = half2(0.0, half(st_grad_len*%s));",
687 delta.fsIn());
688 } else {
689 fragBuilder->codeAppendf("half2 offset = half2(half(st_grad_len*%s), 0.0);",
690 delta.fsIn());
691 }
692 } else if (isSimilarity) {
693 // For a similarity matrix with rotation, the gradient will not be aligned
694 // with the texel coordinate axes, so we need to calculate it.
695 if (args.fShaderCaps->fAvoidDfDxForGradientsWhenPossible) {
696 // We use dFdy instead and rotate -90 degrees to get the gradient in the x
697 // direction.
698 fragBuilder->codeAppendf("half2 st_grad = half2(dFdy(%s));", st.fsIn());
699 if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
700 fragBuilder->codeAppendf("half2 offset = half2(%s)*st_grad;", delta.fsIn());
701 } else {
702 fragBuilder->codeAppendf(
703 "half2 offset = half2(%s*float2(st_grad.y,-st_grad.x));", delta.fsIn());
704 }
705 } else {
706 fragBuilder->codeAppendf("half2 st_grad = half2(dFdx(%s));", st.fsIn());
707 if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
708 fragBuilder->codeAppendf(
709 "half2 offset = half2(%s*float2(-st_grad.y,st_grad.x));", delta.fsIn());
710 } else {
711 fragBuilder->codeAppendf("half2 offset = half(%s)*st_grad;", delta.fsIn());
712 }
713 }
714 fragBuilder->codeAppend("half st_grad_len = length(st_grad);");
715 } else {
716 fragBuilder->codeAppendf("half2 st = half2(%s);\n", st.fsIn());
717
718 fragBuilder->codeAppend("half4 jacobian = half4(dFdx(st), dFdy(st));");
719 if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
720 fragBuilder->codeAppendf("half2 offset = half2(%s)*jacobian.zw;", delta.fsIn());
721 } else {
722 fragBuilder->codeAppendf("half2 offset = half2(%s)*jacobian.xy;", 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(dot(dist_grad, jacobian.xz),"
767 "dot(dist_grad, jacobian.yw));");
768
769 // this gives us a smooth step across approximately one fragment
770 fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
771 }
772
773 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
774 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
775 // mapped linearly to coverage, so use a linear step:
776 if (isGammaCorrect) {
777 fragBuilder->codeAppendf("half4 %s = "
778 "half4(saturate((distance + half3(afwidth)) / half3(2.0 * afwidth)), 1.0);",
779 args.fOutputCoverage);
780 } else {
781 fragBuilder->codeAppendf(
782 "half4 %s = half4(smoothstep(half3(-afwidth), half3(afwidth), distance), 1.0);",
783 args.fOutputCoverage);
784 }
785 }
786
787 private:
788 DistanceAdjust fDistanceAdjust = DistanceAdjust::Make(1.0f, 1.0f, 1.0f);
789 SkISize fAtlasDimensions = {-1, -1};
790 SkMatrix fLocalMatrix = SkMatrix::InvalidMatrix();
791
792 UniformHandle fDistanceAdjustUni;
793 UniformHandle fAtlasDimensionsInvUniform;
794 UniformHandle fLocalMatrixUniform;
795 };
796
797 ///////////////////////////////////////////////////////////////////////////////
798
GrDistanceFieldLCDTextGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,DistanceAdjust distanceAdjust,uint32_t flags,const SkMatrix & localMatrix)799 GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps,
800 const GrSurfaceProxyView* views,
801 int numViews,
802 GrSamplerState params,
803 DistanceAdjust distanceAdjust,
804 uint32_t flags,
805 const SkMatrix& localMatrix)
806 : INHERITED(kGrDistanceFieldLCDTextGeoProc_ClassID)
807 , fLocalMatrix(localMatrix)
808 , fDistanceAdjust(distanceAdjust)
809 , fFlags(flags & kLCD_DistanceFieldEffectMask) {
810 SkASSERT(numViews <= kMaxTextures);
811 SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag));
812
813 if (fFlags & kPerspective_DistanceFieldEffectFlag) {
814 fInPosition = {"inPosition", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
815 } else {
816 fInPosition = {"inPosition", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
817 }
818 fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, SkSLType::kHalf4};
819 fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
820 caps.fIntegerSupport ? SkSLType::kUShort2 : SkSLType::kFloat2};
821 this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
822
823 if (numViews) {
824 fAtlasDimensions = views[0].proxy()->dimensions();
825 }
826
827 for (int i = 0; i < numViews; ++i) {
828 const GrSurfaceProxy* proxy = views[i].proxy();
829 SkASSERT(proxy);
830 SkASSERT(proxy->dimensions() == fAtlasDimensions);
831 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
832 }
833 this->setTextureSamplerCnt(numViews);
834 }
835
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)836 void GrDistanceFieldLCDTextGeoProc::addNewViews(const GrSurfaceProxyView* views,
837 int numViews,
838 GrSamplerState params) {
839 SkASSERT(numViews <= kMaxTextures);
840 // Just to make sure we don't try to add too many proxies
841 numViews = std::min(numViews, kMaxTextures);
842
843 if (!fTextureSamplers[0].isInitialized()) {
844 fAtlasDimensions = views[0].proxy()->dimensions();
845 }
846
847 for (int i = 0; i < numViews; ++i) {
848 const GrSurfaceProxy* proxy = views[i].proxy();
849 SkASSERT(proxy);
850 SkASSERT(proxy->dimensions() == fAtlasDimensions);
851 if (!fTextureSamplers[i].isInitialized()) {
852 fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
853 }
854 }
855 this->setTextureSamplerCnt(numViews);
856 }
857
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const858 void GrDistanceFieldLCDTextGeoProc::addToKey(const GrShaderCaps& caps,
859 skgpu::KeyBuilder* b) const {
860 uint32_t key = 0;
861 key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix);
862 key |= fFlags << 16;
863 b->add32(key);
864 b->add32(this->numTextureSamplers());
865 }
866
makeProgramImpl(const GrShaderCaps &) const867 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldLCDTextGeoProc::makeProgramImpl(
868 const GrShaderCaps&) const {
869 return std::make_unique<Impl>();
870 }
871
872 ///////////////////////////////////////////////////////////////////////////////
873
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc)874 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc)
875
876 #if defined(GR_TEST_UTILS)
877 GrGeometryProcessor* GrDistanceFieldLCDTextGeoProc::TestCreate(GrProcessorTestData* d) {
878 auto [view, ct, at] = d->randomView();
879
880 GrSamplerState::WrapMode wrapModes[2];
881 GrTest::TestWrapModes(d->fRandom, wrapModes);
882 GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
883 ? GrSamplerState::Filter::kLinear
884 : GrSamplerState::Filter::kNearest);
885 DistanceAdjust wa = { 0.0f, 0.1f, -0.1f };
886 uint32_t flags = kUseLCD_DistanceFieldEffectFlag;
887 flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
888 if (flags & kSimilarity_DistanceFieldEffectFlag) {
889 flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
890 }
891 flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0;
892 SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
893
894 return GrDistanceFieldLCDTextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(), &view,
895 1, samplerState, wa, flags, localMatrix);
896 }
897 #endif
898
899 #endif // !defined(SK_DISABLE_SDF_TEXT)
900