1/* 2 * Copyright 2018 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// Equivalent to SkTwoPointConicalGradient::Type 9enum class Type { 10 kRadial, kStrip, kFocal 11}; 12 13in half4x4 gradientMatrix; 14 15layout(key) in Type type; 16layout(key) in bool isRadiusIncreasing; 17 18// Focal-specific optimizations 19layout(key) in bool isFocalOnCircle; 20layout(key) in bool isWellBehaved; 21layout(key) in bool isSwapped; 22layout(key) in bool isNativelyFocal; 23 24// focalParams is interpreted differently depending on if type is focal or degenerate when 25// degenerate, focalParams = (r0, r0^2), so strips will use .y and kRadial will use .x when focal, 26// focalParams = (1/r1, focalX = r0/(r0-r1)) The correct parameters are calculated once in Make for 27// each FP 28layout(tracked) in uniform half2 focalParams; 29 30@coordTransform { 31 gradientMatrix 32} 33 34void main() { 35 // p typed as a float2 is intentional; while a half2 is adequate for most normal cases in the 36 // two point conic gradient's coordinate system, when the gradient is composed with a local 37 // perspective matrix, certain out-of-bounds regions become ill behaved on mobile devices. 38 // On desktops, they are properly clamped after the fact, but on many Adreno GPUs the 39 // calculations of t and x_t below overflow and produce an incorrect interpolant (which then 40 // renders the wrong border color sporadically). Increasing precition alleviates that issue. 41 float2 p = sk_TransformedCoords2D[0]; 42 float t = -1; 43 half v = 1; // validation flag, set to negative to discard fragment later 44 45 @switch(type) { 46 case Type::kStrip: { 47 half r0_2 = focalParams.y; 48 t = r0_2 - p.y * p.y; 49 if (t >= 0) { 50 t = p.x + sqrt(t); 51 } else { 52 v = -1; 53 } 54 } 55 break; 56 case Type::kRadial: { 57 half r0 = focalParams.x; 58 @if(isRadiusIncreasing) { 59 t = length(p) - r0; 60 } else { 61 t = -length(p) - r0; 62 } 63 } 64 break; 65 case Type::kFocal: { 66 half invR1 = focalParams.x; 67 half fx = focalParams.y; 68 69 float x_t = -1; 70 @if (isFocalOnCircle) { 71 x_t = dot(p, p) / p.x; 72 } else if (isWellBehaved) { 73 x_t = length(p) - p.x * invR1; 74 } else { 75 float temp = p.x * p.x - p.y * p.y; 76 77 // Only do sqrt if temp >= 0; this is significantly slower than checking temp >= 0 78 // in the if statement that checks r(t) >= 0. But GPU may break if we sqrt a 79 // negative float. (Although I havevn't observed that on any devices so far, and the 80 // old approach also does sqrt negative value without a check.) If the performance 81 // is really critical, maybe we should just compute the area where temp and x_t are 82 // always valid and drop all these ifs. 83 if (temp >= 0) { 84 @if(isSwapped || !isRadiusIncreasing) { 85 x_t = -sqrt(temp) - p.x * invR1; 86 } else { 87 x_t = sqrt(temp) - p.x * invR1; 88 } 89 } 90 } 91 92 // The final calculation of t from x_t has lots of static optimizations but only do them 93 // when x_t is positive (which can be assumed true if isWellBehaved is true) 94 @if (!isWellBehaved) { 95 // This will still calculate t even though it will be ignored later in the pipeline 96 // to avoid a branch 97 if (x_t <= 0.0) { 98 v = -1; 99 } 100 } 101 @if (isRadiusIncreasing) { 102 @if (isNativelyFocal) { 103 t = x_t; 104 } else { 105 t = x_t + fx; 106 } 107 } else { 108 @if (isNativelyFocal) { 109 t = -x_t; 110 } else { 111 t = -x_t + fx; 112 } 113 } 114 115 @if(isSwapped) { 116 t = 1 - t; 117 } 118 } 119 break; 120 } 121 122 sk_OutColor = half4(t, v, 0, 0); 123} 124 125////////////////////////////////////////////////////////////////////////////// 126 127@header { 128 #include "SkTwoPointConicalGradient.h" 129 #include "GrGradientShader.h" 130} 131 132// The 2 point conical gradient can reject a pixel so it does change opacity 133// even if the input was opaque, so disable that optimization 134@optimizationFlags { 135 kNone_OptimizationFlags 136} 137 138@make { 139 static std::unique_ptr<GrFragmentProcessor> Make(const SkTwoPointConicalGradient& gradient, 140 const GrFPArgs& args); 141} 142 143@cppEnd { 144 // .fp files do not let you reference outside enum definitions, so we have to explicitly map 145 // between the two compatible enum defs 146 GrTwoPointConicalGradientLayout::Type convert_type( 147 SkTwoPointConicalGradient::Type type) { 148 switch(type) { 149 case SkTwoPointConicalGradient::Type::kRadial: 150 return GrTwoPointConicalGradientLayout::Type::kRadial; 151 case SkTwoPointConicalGradient::Type::kStrip: 152 return GrTwoPointConicalGradientLayout::Type::kStrip; 153 case SkTwoPointConicalGradient::Type::kFocal: 154 return GrTwoPointConicalGradientLayout::Type::kFocal; 155 } 156 SkDEBUGFAIL("Should not be reachable"); 157 return GrTwoPointConicalGradientLayout::Type::kRadial; 158 } 159 160 std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::Make( 161 const SkTwoPointConicalGradient& grad, const GrFPArgs& args) { 162 GrTwoPointConicalGradientLayout::Type grType = convert_type(grad.getType()); 163 164 // The focalData struct is only valid if isFocal is true 165 const SkTwoPointConicalGradient::FocalData& focalData = grad.getFocalData(); 166 bool isFocal = grType == Type::kFocal; 167 168 // Calculate optimization switches from gradient specification 169 bool isFocalOnCircle = isFocal && focalData.isFocalOnCircle(); 170 bool isWellBehaved = isFocal && focalData.isWellBehaved(); 171 bool isSwapped = isFocal && focalData.isSwapped(); 172 bool isNativelyFocal = isFocal && focalData.isNativelyFocal(); 173 174 // Type-specific calculations: isRadiusIncreasing, focalParams, and the gradient matrix. 175 // However, all types start with the total inverse local matrix calculated from the shader 176 // and args 177 bool isRadiusIncreasing; 178 SkPoint focalParams; // really just a 2D tuple 179 SkMatrix matrix; 180 181 // Initialize the base matrix 182 if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { 183 return nullptr; 184 } 185 186 if (isFocal) { 187 isRadiusIncreasing = (1 - focalData.fFocalX) > 0; 188 189 focalParams.set(1.0 / focalData.fR1, focalData.fFocalX); 190 191 matrix.postConcat(grad.getGradientMatrix()); 192 } else if (grType == Type::kRadial) { 193 SkScalar dr = grad.getDiffRadius(); 194 isRadiusIncreasing = dr >= 0; 195 196 SkScalar r0 = grad.getStartRadius() / dr; 197 focalParams.set(r0, r0 * r0); 198 199 200 // GPU radial matrix is different from the original matrix, since we map the diff radius 201 // to have |dr| = 1, so manually compute the final gradient matrix here. 202 203 // Map center to (0, 0) 204 matrix.postTranslate(-grad.getStartCenter().fX, -grad.getStartCenter().fY); 205 206 // scale |diffRadius| to 1 207 matrix.postScale(1 / dr, 1 / dr); 208 } else { // kStrip 209 isRadiusIncreasing = false; // kStrip doesn't use this flag 210 211 SkScalar r0 = grad.getStartRadius() / grad.getCenterX1(); 212 focalParams.set(r0, r0 * r0); 213 214 215 matrix.postConcat(grad.getGradientMatrix()); 216 } 217 218 219 return std::unique_ptr<GrFragmentProcessor>(new GrTwoPointConicalGradientLayout( 220 matrix, grType, isRadiusIncreasing, isFocalOnCircle, isWellBehaved, 221 isSwapped, isNativelyFocal, focalParams)); 222 } 223} 224 225////////////////////////////////////////////////////////////////////////////// 226 227@test(d) { 228 SkScalar scale = GrGradientShader::RandomParams::kGradientScale; 229 SkScalar offset = scale / 32.0f; 230 231 SkPoint center1 = {d->fRandom->nextRangeScalar(0.0f, scale), 232 d->fRandom->nextRangeScalar(0.0f, scale)}; 233 SkPoint center2 = {d->fRandom->nextRangeScalar(0.0f, scale), 234 d->fRandom->nextRangeScalar(0.0f, scale)}; 235 SkScalar radius1 = d->fRandom->nextRangeScalar(0.0f, scale); 236 SkScalar radius2 = d->fRandom->nextRangeScalar(0.0f, scale); 237 238 constexpr int kTestTypeMask = (1 << 2) - 1, 239 kTestNativelyFocalBit = (1 << 2), 240 kTestFocalOnCircleBit = (1 << 3), 241 kTestSwappedBit = (1 << 4); 242 // We won't treat isWellDefined and isRadiusIncreasing specially because they 243 // should have high probability to be turned on and off as we're getting random 244 // radii and centers. 245 246 int mask = d->fRandom->nextU(); 247 int type = mask & kTestTypeMask; 248 if (type == static_cast<int>(Type::kRadial)) { 249 center2 = center1; 250 // Make sure that the radii are different 251 if (SkScalarNearlyZero(radius1 - radius2)) { 252 radius2 += offset; 253 } 254 } else if (type == static_cast<int>(Type::kStrip)) { 255 radius1 = SkTMax(radius1, .1f); // Make sure that the radius is non-zero 256 radius2 = radius1; 257 // Make sure that the centers are different 258 if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { 259 center2.fX += offset; 260 } 261 } else { // kFocal_Type 262 // Make sure that the centers are different 263 if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { 264 center2.fX += offset; 265 } 266 267 if (kTestNativelyFocalBit & mask) { 268 radius1 = 0; 269 } 270 if (kTestFocalOnCircleBit & mask) { 271 radius2 = radius1 + SkPoint::Distance(center1, center2); 272 } 273 if (kTestSwappedBit & mask) { 274 std::swap(radius1, radius2); 275 radius2 = 0; 276 } 277 278 // Make sure that the radii are different 279 if (SkScalarNearlyZero(radius1 - radius2)) { 280 radius2 += offset; 281 } 282 } 283 284 if (SkScalarNearlyZero(radius1 - radius2) && 285 SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { 286 radius2 += offset; // make sure that we're not degenerated 287 } 288 289 GrGradientShader::RandomParams params(d->fRandom); 290 auto shader = params.fUseColors4f ? 291 SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, 292 params.fColors4f, params.fColorSpace, params.fStops, 293 params.fColorCount, params.fTileMode) : 294 SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, 295 params.fColors, params.fStops, 296 params.fColorCount, params.fTileMode); 297 GrTest::TestAsFPArgs asFPArgs(d); 298 std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); 299 300 GrAlwaysAssert(fp); 301 return fp; 302} 303