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