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