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