Home | History | Annotate | Download | only in gradients
      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 // Unrolled gradient code supporting up to 8 intervals that produces code
      9 // targeting a specific interval count.
     10 
     11 // Assumed to be between 1 and 8.
     12 layout(key) in int intervalCount;
     13 
     14 // With the current hardstop detection threshold of 0.00024, the maximum scale and bias values
     15 // will be on the order of 4k (since they divide by dt). That is well outside the precision
     16 // capabilities of half floats, which can lead to inaccurate gradient calculations
     17 layout(ctype=SkPMColor4f) in uniform float4 scale0_1;
     18 layout(ctype=SkPMColor4f, when=intervalCount > 1) in uniform float4 scale2_3;
     19 layout(ctype=SkPMColor4f, when=intervalCount > 2) in uniform float4 scale4_5;
     20 layout(ctype=SkPMColor4f, when=intervalCount > 3) in uniform float4 scale6_7;
     21 layout(ctype=SkPMColor4f, when=intervalCount > 4) in uniform float4 scale8_9;
     22 layout(ctype=SkPMColor4f, when=intervalCount > 5) in uniform float4 scale10_11;
     23 layout(ctype=SkPMColor4f, when=intervalCount > 6) in uniform float4 scale12_13;
     24 layout(ctype=SkPMColor4f, when=intervalCount > 7) in uniform float4 scale14_15;
     25 
     26 layout(ctype=SkPMColor4f) in uniform float4 bias0_1;
     27 layout(ctype=SkPMColor4f, when=intervalCount > 1) in uniform float4 bias2_3;
     28 layout(ctype=SkPMColor4f, when=intervalCount > 2) in uniform float4 bias4_5;
     29 layout(ctype=SkPMColor4f, when=intervalCount > 3) in uniform float4 bias6_7;
     30 layout(ctype=SkPMColor4f, when=intervalCount > 4) in uniform float4 bias8_9;
     31 layout(ctype=SkPMColor4f, when=intervalCount > 5) in uniform float4 bias10_11;
     32 layout(ctype=SkPMColor4f, when=intervalCount > 6) in uniform float4 bias12_13;
     33 layout(ctype=SkPMColor4f, when=intervalCount > 7) in uniform float4 bias14_15;
     34 
     35 // The 7 threshold positions that define the boundaries of the 8 intervals (excluding t = 0, and t =
     36 // 1) are packed into two half4's instead of having up to 7 separate scalar uniforms. For low
     37 // interval counts, the extra components are ignored in the shader, but the uniform simplification
     38 // is worth it. It is assumed thresholds are provided in increasing value, mapped as:
     39 //  - thresholds1_7.x = boundary between (0,1) and (2,3) -> 1_2
     40 //  -              .y = boundary between (2,3) and (4,5) -> 3_4
     41 //  -              .z = boundary between (4,5) and (6,7) -> 5_6
     42 //  -              .w = boundary between (6,7) and (8,9) -> 7_8
     43 //  - thresholds9_13.x = boundary between (8,9) and (10,11) -> 9_10
     44 //  -               .y = boundary between (10,11) and (12,13) -> 11_12
     45 //  -               .z = boundary between (12,13) and (14,15) -> 13_14
     46 //  -               .w = unused
     47 in uniform half4 thresholds1_7;
     48 in uniform half4 thresholds9_13;
     49 
     50 void main() {
     51     half t = sk_InColor.x;
     52 
     53     float4 scale, bias;
     54     // Explicit binary search for the proper interval that t falls within. The interval count
     55     // checks are converted into constant expressions in the C++ generated SkSL, which are then
     56     // optimized to the minimal number of branches for the specific interval count.
     57 
     58     // thresholds1_7.w is mid point for intervals (0,7) and (8,15)
     59     if (intervalCount <= 4 || t < thresholds1_7.w) {
     60         // thresholds1_7.y is mid point for intervals (0,3) and (4,7)
     61         if (intervalCount <= 2 || t < thresholds1_7.y) {
     62             // thresholds1_7.x is mid point for intervals (0,1) and (2,3)
     63             if (intervalCount <= 1 || t < thresholds1_7.x) {
     64                 scale = scale0_1;
     65                 bias = bias0_1;
     66             } else {
     67                 scale = scale2_3;
     68                 bias = bias2_3;
     69             }
     70         } else {
     71             // thresholds1_7.z is mid point for intervals (4,5) and (6,7)
     72             if (intervalCount <= 3 || t < thresholds1_7.z) {
     73                 scale = scale4_5;
     74                 bias = bias4_5;
     75             } else {
     76                 scale = scale6_7;
     77                 bias = bias6_7;
     78             }
     79         }
     80     } else {
     81         // thresholds9_13.y is mid point for intervals (8,11) and (12,15)
     82         if (intervalCount <= 6 || t < thresholds9_13.y) {
     83             // thresholds9_13.x is mid point for intervals (8,9) and (10,11)
     84             if (intervalCount <= 5 || t < thresholds9_13.x) {
     85                 // interval 8-9
     86                 scale = scale8_9;
     87                 bias = bias8_9;
     88             } else {
     89                 // interval 10-11
     90                 scale = scale10_11;
     91                 bias = bias10_11;
     92             }
     93         } else {
     94             // thresholds9_13.z is mid point for intervals (12,13) and (14,15)
     95             if (intervalCount <= 7 || t < thresholds9_13.z) {
     96                 // interval 12-13
     97                 scale = scale12_13;
     98                 bias = bias12_13;
     99             } else {
    100                 // interval 14-15
    101                 scale = scale14_15;
    102                 bias = bias14_15;
    103             }
    104         }
    105     }
    106 
    107     sk_OutColor = half4(t * scale + bias);
    108 }
    109 
    110 //////////////////////////////////////////////////////////////////////////////
    111 
    112 @class {
    113     static const int kMaxColorCount = 16;
    114 }
    115 
    116 @make {
    117     static std::unique_ptr<GrFragmentProcessor> Make(const SkPMColor4f* colors,
    118                                                      const SkScalar* positions,
    119                                                      int count);
    120 }
    121 
    122 @cppEnd {
    123     static const int kMaxIntervals = 8;
    124     std::unique_ptr<GrFragmentProcessor> GrUnrolledBinaryGradientColorizer::Make(
    125             const SkPMColor4f* colors, const SkScalar* positions, int count) {
    126         // Depending on how the positions resolve into hard stops or regular stops, the number of
    127         // intervals specified by the number of colors/positions can change. For instance, a plain
    128         // 3 color gradient is two intervals, but a 4 color gradient with a hard stop is also
    129         // two intervals. At the most extreme end, an 8 interval gradient made entirely of hard
    130         // stops has 16 colors.
    131 
    132         if (count > kMaxColorCount) {
    133             // Definitely cannot represent this gradient configuration
    134             return nullptr;
    135         }
    136 
    137         // The raster implementation also uses scales and biases, but since they must be calculated
    138         // after the dst color space is applied, it limits our ability to cache their values.
    139         SkPMColor4f scales[kMaxIntervals];
    140         SkPMColor4f biases[kMaxIntervals];
    141         SkScalar thresholds[kMaxIntervals];
    142 
    143         int intervalCount = 0;
    144 
    145         for (int i = 0; i < count - 1; i++) {
    146             if (intervalCount >= kMaxIntervals) {
    147                 // Already reached kMaxIntervals, and haven't run out of color stops so this
    148                 // gradient cannot be represented by this shader.
    149                 return nullptr;
    150             }
    151 
    152             SkScalar t0 = positions[i];
    153             SkScalar t1 = positions[i + 1];
    154             SkScalar dt = t1 - t0;
    155             // If the interval is empty, skip to the next interval. This will automatically create
    156             // distinct hard stop intervals as needed. It also protects against malformed gradients
    157             // that have repeated hard stops at the very beginning that are effectively unreachable.
    158             if (SkScalarNearlyZero(dt)) {
    159                 continue;
    160             }
    161 
    162             auto c0 = Sk4f::Load(colors[i].vec());
    163             auto c1 = Sk4f::Load(colors[i + 1].vec());
    164 
    165             auto scale = (c1 - c0) / dt;
    166             auto bias = c0 - t0 * scale;
    167 
    168             scale.store(scales + intervalCount);
    169             bias.store(biases + intervalCount);
    170             thresholds[intervalCount] = t1;
    171             intervalCount++;
    172         }
    173 
    174         // For isEqual to make sense, set the unused values to something consistent
    175         for (int i = intervalCount; i < kMaxIntervals; i++) {
    176             scales[i] = SK_PMColor4fTRANSPARENT;
    177             biases[i] = SK_PMColor4fTRANSPARENT;
    178             thresholds[i] = 0.0;
    179         }
    180 
    181         return std::unique_ptr<GrFragmentProcessor>(new GrUnrolledBinaryGradientColorizer(
    182                 intervalCount, scales[0], scales[1], scales[2], scales[3], scales[4], scales[5],
    183                 scales[6], scales[7], biases[0], biases[1], biases[2], biases[3], biases[4],
    184                 biases[5], biases[6], biases[7],
    185                 SkRect::MakeLTRB(thresholds[0], thresholds[1], thresholds[2], thresholds[3]),
    186                 SkRect::MakeLTRB(thresholds[4], thresholds[5], thresholds[6], 0.0)));
    187     }
    188 }
    189