123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- /*
- * 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 half4x4 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 = SkTMax(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());
- GrAlwaysAssert(fp);
- return fp;
- }
|