| /* | |
| * Copyright 2018 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| // Equivalent to SkTwoPointConicalGradient::Type | |
| enum class Type { | |
| kRadial, kStrip, kFocal | |
| }; | |
| in half3x3 gradientMatrix; | |
| layout(key) in Type type; | |
| layout(key) in bool isRadiusIncreasing; | |
| // Focal-specific optimizations | |
| layout(key) in bool isFocalOnCircle; | |
| layout(key) in bool isWellBehaved; | |
| layout(key) in bool isSwapped; | |
| layout(key) in bool isNativelyFocal; | |
| // focalParams is interpreted differently depending on if type is focal or degenerate when | |
| // degenerate, focalParams = (r0, r0^2), so strips will use .y and kRadial will use .x when focal, | |
| // focalParams = (1/r1, focalX = r0/(r0-r1)) The correct parameters are calculated once in Make for | |
| // each FP | |
| layout(tracked) in uniform half2 focalParams; | |
| @coordTransform { | |
| gradientMatrix | |
| } | |
| void main() { | |
| // p typed as a float2 is intentional; while a half2 is adequate for most normal cases in the | |
| // two point conic gradient's coordinate system, when the gradient is composed with a local | |
| // perspective matrix, certain out-of-bounds regions become ill behaved on mobile devices. | |
| // On desktops, they are properly clamped after the fact, but on many Adreno GPUs the | |
| // calculations of t and x_t below overflow and produce an incorrect interpolant (which then | |
| // renders the wrong border color sporadically). Increasing precition alleviates that issue. | |
| float2 p = sk_TransformedCoords2D[0]; | |
| float t = -1; | |
| half v = 1; // validation flag, set to negative to discard fragment later | |
| @switch(type) { | |
| case Type::kStrip: { | |
| half r0_2 = focalParams.y; | |
| t = r0_2 - p.y * p.y; | |
| if (t >= 0) { | |
| t = p.x + sqrt(t); | |
| } else { | |
| v = -1; | |
| } | |
| } | |
| break; | |
| case Type::kRadial: { | |
| half r0 = focalParams.x; | |
| @if(isRadiusIncreasing) { | |
| t = length(p) - r0; | |
| } else { | |
| t = -length(p) - r0; | |
| } | |
| } | |
| break; | |
| case Type::kFocal: { | |
| half invR1 = focalParams.x; | |
| half fx = focalParams.y; | |
| float x_t = -1; | |
| @if (isFocalOnCircle) { | |
| x_t = dot(p, p) / p.x; | |
| } else if (isWellBehaved) { | |
| x_t = length(p) - p.x * invR1; | |
| } else { | |
| float temp = p.x * p.x - p.y * p.y; | |
| // Only do sqrt if temp >= 0; this is significantly slower than checking temp >= 0 | |
| // in the if statement that checks r(t) >= 0. But GPU may break if we sqrt a | |
| // negative float. (Although I havevn't observed that on any devices so far, and the | |
| // old approach also does sqrt negative value without a check.) If the performance | |
| // is really critical, maybe we should just compute the area where temp and x_t are | |
| // always valid and drop all these ifs. | |
| if (temp >= 0) { | |
| @if(isSwapped || !isRadiusIncreasing) { | |
| x_t = -sqrt(temp) - p.x * invR1; | |
| } else { | |
| x_t = sqrt(temp) - p.x * invR1; | |
| } | |
| } | |
| } | |
| // The final calculation of t from x_t has lots of static optimizations but only do them | |
| // when x_t is positive (which can be assumed true if isWellBehaved is true) | |
| @if (!isWellBehaved) { | |
| // This will still calculate t even though it will be ignored later in the pipeline | |
| // to avoid a branch | |
| if (x_t <= 0.0) { | |
| v = -1; | |
| } | |
| } | |
| @if (isRadiusIncreasing) { | |
| @if (isNativelyFocal) { | |
| t = x_t; | |
| } else { | |
| t = x_t + fx; | |
| } | |
| } else { | |
| @if (isNativelyFocal) { | |
| t = -x_t; | |
| } else { | |
| t = -x_t + fx; | |
| } | |
| } | |
| @if(isSwapped) { | |
| t = 1 - t; | |
| } | |
| } | |
| break; | |
| } | |
| sk_OutColor = half4(half(t), v, 0, 0); | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| @header { | |
| #include "src/gpu/gradients/GrGradientShader.h" | |
| #include "src/shaders/gradients/SkTwoPointConicalGradient.h" | |
| } | |
| // The 2 point conical gradient can reject a pixel so it does change opacity | |
| // even if the input was opaque, so disable that optimization | |
| @optimizationFlags { | |
| kNone_OptimizationFlags | |
| } | |
| @make { | |
| static std::unique_ptr<GrFragmentProcessor> Make(const SkTwoPointConicalGradient& gradient, | |
| const GrFPArgs& args); | |
| } | |
| @cppEnd { | |
| // .fp files do not let you reference outside enum definitions, so we have to explicitly map | |
| // between the two compatible enum defs | |
| GrTwoPointConicalGradientLayout::Type convert_type( | |
| SkTwoPointConicalGradient::Type type) { | |
| switch(type) { | |
| case SkTwoPointConicalGradient::Type::kRadial: | |
| return GrTwoPointConicalGradientLayout::Type::kRadial; | |
| case SkTwoPointConicalGradient::Type::kStrip: | |
| return GrTwoPointConicalGradientLayout::Type::kStrip; | |
| case SkTwoPointConicalGradient::Type::kFocal: | |
| return GrTwoPointConicalGradientLayout::Type::kFocal; | |
| } | |
| SkDEBUGFAIL("Should not be reachable"); | |
| return GrTwoPointConicalGradientLayout::Type::kRadial; | |
| } | |
| std::unique_ptr<GrFragmentProcessor> GrTwoPointConicalGradientLayout::Make( | |
| const SkTwoPointConicalGradient& grad, const GrFPArgs& args) { | |
| GrTwoPointConicalGradientLayout::Type grType = convert_type(grad.getType()); | |
| // The focalData struct is only valid if isFocal is true | |
| const SkTwoPointConicalGradient::FocalData& focalData = grad.getFocalData(); | |
| bool isFocal = grType == Type::kFocal; | |
| // Calculate optimization switches from gradient specification | |
| bool isFocalOnCircle = isFocal && focalData.isFocalOnCircle(); | |
| bool isWellBehaved = isFocal && focalData.isWellBehaved(); | |
| bool isSwapped = isFocal && focalData.isSwapped(); | |
| bool isNativelyFocal = isFocal && focalData.isNativelyFocal(); | |
| // Type-specific calculations: isRadiusIncreasing, focalParams, and the gradient matrix. | |
| // However, all types start with the total inverse local matrix calculated from the shader | |
| // and args | |
| bool isRadiusIncreasing; | |
| SkPoint focalParams; // really just a 2D tuple | |
| SkMatrix matrix; | |
| // Initialize the base matrix | |
| if (!grad.totalLocalMatrix(args.fPreLocalMatrix, args.fPostLocalMatrix)->invert(&matrix)) { | |
| return nullptr; | |
| } | |
| if (isFocal) { | |
| isRadiusIncreasing = (1 - focalData.fFocalX) > 0; | |
| focalParams.set(1.0 / focalData.fR1, focalData.fFocalX); | |
| matrix.postConcat(grad.getGradientMatrix()); | |
| } else if (grType == Type::kRadial) { | |
| SkScalar dr = grad.getDiffRadius(); | |
| isRadiusIncreasing = dr >= 0; | |
| SkScalar r0 = grad.getStartRadius() / dr; | |
| focalParams.set(r0, r0 * r0); | |
| // GPU radial matrix is different from the original matrix, since we map the diff radius | |
| // to have |dr| = 1, so manually compute the final gradient matrix here. | |
| // Map center to (0, 0) | |
| matrix.postTranslate(-grad.getStartCenter().fX, -grad.getStartCenter().fY); | |
| // scale |diffRadius| to 1 | |
| matrix.postScale(1 / dr, 1 / dr); | |
| } else { // kStrip | |
| isRadiusIncreasing = false; // kStrip doesn't use this flag | |
| SkScalar r0 = grad.getStartRadius() / grad.getCenterX1(); | |
| focalParams.set(r0, r0 * r0); | |
| matrix.postConcat(grad.getGradientMatrix()); | |
| } | |
| return std::unique_ptr<GrFragmentProcessor>(new GrTwoPointConicalGradientLayout( | |
| matrix, grType, isRadiusIncreasing, isFocalOnCircle, isWellBehaved, | |
| isSwapped, isNativelyFocal, focalParams)); | |
| } | |
| } | |
| ////////////////////////////////////////////////////////////////////////////// | |
| @test(d) { | |
| SkScalar scale = GrGradientShader::RandomParams::kGradientScale; | |
| SkScalar offset = scale / 32.0f; | |
| SkPoint center1 = {d->fRandom->nextRangeScalar(0.0f, scale), | |
| d->fRandom->nextRangeScalar(0.0f, scale)}; | |
| SkPoint center2 = {d->fRandom->nextRangeScalar(0.0f, scale), | |
| d->fRandom->nextRangeScalar(0.0f, scale)}; | |
| SkScalar radius1 = d->fRandom->nextRangeScalar(0.0f, scale); | |
| SkScalar radius2 = d->fRandom->nextRangeScalar(0.0f, scale); | |
| constexpr int kTestTypeMask = (1 << 2) - 1, | |
| kTestNativelyFocalBit = (1 << 2), | |
| kTestFocalOnCircleBit = (1 << 3), | |
| kTestSwappedBit = (1 << 4); | |
| // We won't treat isWellDefined and isRadiusIncreasing specially because they | |
| // should have high probability to be turned on and off as we're getting random | |
| // radii and centers. | |
| int mask = d->fRandom->nextU(); | |
| int type = mask & kTestTypeMask; | |
| if (type == static_cast<int>(Type::kRadial)) { | |
| center2 = center1; | |
| // Make sure that the radii are different | |
| if (SkScalarNearlyZero(radius1 - radius2)) { | |
| radius2 += offset; | |
| } | |
| } else if (type == static_cast<int>(Type::kStrip)) { | |
| radius1 = std::max(radius1, .1f); // Make sure that the radius is non-zero | |
| radius2 = radius1; | |
| // Make sure that the centers are different | |
| if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { | |
| center2.fX += offset; | |
| } | |
| } else { // kFocal_Type | |
| // Make sure that the centers are different | |
| if (SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { | |
| center2.fX += offset; | |
| } | |
| if (kTestNativelyFocalBit & mask) { | |
| radius1 = 0; | |
| } | |
| if (kTestFocalOnCircleBit & mask) { | |
| radius2 = radius1 + SkPoint::Distance(center1, center2); | |
| } | |
| if (kTestSwappedBit & mask) { | |
| std::swap(radius1, radius2); | |
| radius2 = 0; | |
| } | |
| // Make sure that the radii are different | |
| if (SkScalarNearlyZero(radius1 - radius2)) { | |
| radius2 += offset; | |
| } | |
| } | |
| if (SkScalarNearlyZero(radius1 - radius2) && | |
| SkScalarNearlyZero(SkPoint::Distance(center1, center2))) { | |
| radius2 += offset; // make sure that we're not degenerated | |
| } | |
| GrGradientShader::RandomParams params(d->fRandom); | |
| auto shader = params.fUseColors4f ? | |
| SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, | |
| params.fColors4f, params.fColorSpace, params.fStops, | |
| params.fColorCount, params.fTileMode) : | |
| SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, | |
| params.fColors, params.fStops, | |
| params.fColorCount, params.fTileMode); | |
| GrTest::TestAsFPArgs asFPArgs(d); | |
| std::unique_ptr<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); | |
| SkASSERT_RELEASE(fp); | |
| return fp; | |
| } |