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