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