tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ps_quad_gradient.glsl (13598B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2  * License, v. 2.0. If a copy of the MPL was not distributed with this
      3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /// This shader renders any kind of css gradents in a color or alpha target.
      6 
      7 #include ps_quad
      8 #include dithering
      9 
     10 #define PI 3.141592653589793
     11 
     12 #define GRADIENT_KIND_LINEAR 0
     13 #define GRADIENT_KIND_RADIAL 1
     14 #define GRADIENT_KIND_CONIC  2
     15 
     16 // All of the integer varyings are packed into this header (see decode_gradient_header).
     17 flat varying highp ivec4 v_gradient_header;
     18 // Gradient-specific varying parameters are packed into these two varyings.
     19 varying highp vec4 v_interpolated_data;
     20 flat varying highp vec4 v_flat_data;
     21 // The first four stop offsets provided to the fragment shader to reduce the
     22 // number of gpu buffer reads in the common case.
     23 flat varying mediump vec4 v_stop_offsets;
     24 // Two color stops provided via varyings, only used in the fast path with only
     25 // two color stops.
     26 flat varying mediump vec4 v_color0;
     27 flat varying mediump vec4 v_color1;
     28 
     29 #ifdef WR_VERTEX_SHADER
     30 
     31 void linear_gradient_vertex(vec2 position, vec4 data0) {
     32     vec2 p0 = data0.xy;
     33     vec2 p1 = data0.zw;
     34 
     35     vec2 dir = p1 - p0;
     36     dir = dir / dot(dir, dir);
     37     float offset = dot(p0, dir);
     38 
     39     v_interpolated_data = vec4(position, 0.0, 0.0);
     40     v_flat_data = vec4(dir, offset, 0.0);
     41 }
     42 
     43 void radial_gradient_vertex(vec2 position, vec4 data0, vec4 data1) {
     44     vec2 center = data0.xy;
     45     vec2 scale = data0.zw;
     46     float start_radius = data1.x;
     47     float end_radius = data1.y;
     48     float xy_ratio = data1.z;
     49 
     50     // Store 1/rd where rd = end_radius - start_radius
     51     // If rd = 0, we can't get its reciprocal. Instead, just use a zero scale.
     52     float rd = end_radius - start_radius;
     53     float radius_scale = rd != 0.0 ? 1.0 / rd : 0.0;
     54 
     55     // Transform all coordinates by the y scale so the
     56     // fragment shader can work with circles
     57 
     58     // v_pos is in a coordinate space relative to the task rect
     59     // (so it is independent of the task origin).
     60     start_radius = start_radius * radius_scale;
     61     vec2 normalized_pos = (position * scale - center) * radius_scale;
     62     normalized_pos.y *= xy_ratio;
     63 
     64     v_interpolated_data = vec4(normalized_pos.x, normalized_pos.y, 0.0, 0.0);
     65     v_flat_data = vec4(start_radius, 0.0, 0.0, 0.0);
     66 }
     67 
     68 void conic_gradient_vertex(vec2 position, vec4 data0, vec4 data1) {
     69     vec2 center = data0.xy;
     70     vec2 scale = data0.zw;
     71     float start_offset = data1.x;
     72     float end_offset = data1.y;
     73     float angle = PI / 2.0 - data1.z;
     74 
     75     // Store 1/d where d = end_offset - start_offset
     76     // If d = 0, we can't get its reciprocal. Instead, just use a zero scale.
     77     float d = end_offset - start_offset;
     78     float offset_scale = d != 0.0 ? 1.0 / d : 0.0;
     79 
     80     start_offset = start_offset * offset_scale;
     81     vec2 dir = (position * scale - center);
     82 
     83     v_interpolated_data = vec4(dir, start_offset, offset_scale);
     84     v_flat_data = vec4(angle, 0.0, 0.0, 0.0);
     85 }
     86 
     87 ivec4 decode_gradient_header(int base_address, vec4 payload) {
     88     int kind = int(payload.x);
     89     int count = int(payload.y);
     90     int extend_mode = int(payload.z);
     91     int colors_address = base_address + 1;
     92 
     93     return ivec4(
     94         kind,
     95         count,
     96         extend_mode,
     97         colors_address
     98     );
     99 }
    100 
    101 void pattern_vertex(PrimitiveInfo info) {
    102     int address = info.pattern_input.x;
    103     // gradient[0..1] contains linear/radial/conic specific data,
    104     // gradient[2] contains the header to interpret gradient stops.
    105     vec4[3] gradient = fetch_from_gpu_buffer_3f(address);
    106     ivec4 header = decode_gradient_header(address + 2, gradient[2]);
    107 
    108     vec2 pos = info.local_pos - info.local_prim_rect.p0;
    109 
    110     switch (header.x) {
    111         case GRADIENT_KIND_LINEAR: {
    112             linear_gradient_vertex(pos, gradient[0]);
    113             break;
    114         }
    115         case GRADIENT_KIND_RADIAL: {
    116             radial_gradient_vertex(pos, gradient[0], gradient[1]);
    117             break;
    118         }
    119         case GRADIENT_KIND_CONIC: {
    120             conic_gradient_vertex(pos, gradient[0], gradient[1]);
    121             break;
    122         }
    123         default: {
    124             // This should be dead code.
    125             v_interpolated_data = vec4(0.0);
    126             v_flat_data = vec4(0.0);
    127             break;
    128         }
    129     }
    130 
    131     int count = header.y;
    132     int colors_addr = header.w;
    133     int offsets_addrs = colors_addr + count;
    134 
    135     v_stop_offsets = fetch_from_gpu_buffer_1f(offsets_addrs);
    136     v_gradient_header = header;
    137 
    138     if (count == 2) {
    139         // Fast path: If we have only two color stops, pass them by varyings.
    140         vec4[2] colors = fetch_from_gpu_buffer_2f(colors_addr);
    141         v_color0 = colors[0];
    142         v_color1 = colors[1];
    143     }
    144 }
    145 
    146 #endif
    147 
    148 
    149 #ifdef WR_FRAGMENT_SHADER
    150 
    151 // From https://math.stackexchange.com/questions/1098487/atan2-faster-approximation
    152 float approx_atan2(float y, float x) {
    153     vec2 a = abs(vec2(x, y));
    154     float slope = min(a.x, a.y) / max(a.x, a.y);
    155     float s2 = slope * slope;
    156     float r = ((-0.0464964749 * s2 + 0.15931422) * s2 - 0.327622764) * s2 * slope + slope;
    157 
    158     r = if_then_else(float(a.y > a.x), 1.57079637 - r, r);
    159     r = if_then_else(float(x < 0.0),   3.14159274 - r, r);
    160     // To match atan2's behavior, -0.0 should count as negative and flip the sign of r.
    161     // Does this matter in practice in the context of conic gradients?
    162     r = y < 0.0 ? -r : r;
    163 
    164     return r;
    165 }
    166 
    167 float apply_extend_mode(float offset) {
    168     // Handle the repeat mode.
    169     float mode = float(v_gradient_header.z);
    170     offset -= floor(offset) * mode;
    171 
    172     return offset;
    173 }
    174 
    175 // Sample the gradient using a sequence of gradient stops located at the provided
    176 // addresses.
    177 //
    178 // See the comment above `prim_store::gradient::write_gpu_gradient_stops_tree` about
    179 // the layout of the gradient data in the gpu buffer.
    180 vec4 sample_gradient_stops_tree(float offset) {
    181     int count = v_gradient_header.y;
    182     int colors_addr = v_gradient_header.w;
    183     // Address of the current level
    184     int level_base_addr = colors_addr + count;
    185     // Number of blocks of 4 indices for the current level.
    186     // At the root, a single block is stored. Each level stores
    187     // 5 times more blocks than the previous one.
    188     int level_stride = 1;
    189     // Relative address within the current level.
    190     int offset_in_level = 0;
    191     // Current gradient stop index.
    192     int index = 0;
    193     // The index distance between consecutive stop offsets at
    194     // the current level. At the last level, the stride is 1.
    195     // each has a 5 times more stride than the next (so the
    196     // index stride starts high and is divided by 5 at each
    197     // iteration).
    198     int index_stride = 1;
    199     while (index_stride * 5 <= count) {
    200         index_stride *= 5;
    201     }
    202 
    203     // The offsets of the stops before and after the target offset.
    204     // They will converge to the correct values as the tree is
    205     // traversed.
    206     float prev_offset = 1.0;
    207     float next_offset = 0.0;
    208 
    209     // First offsets are the root level.
    210     vec4 current_stops = v_stop_offsets;
    211 
    212     // This could be a while(true) since we are going to exit this loop
    213     // its break statement, but we get miscompilations with while(true)
    214     // so instead we put a dummy condition that always evaluates to true.
    215     while (index_stride > 0) {
    216         // Determine which of the five partitions (sub-trees)
    217         // to take next.
    218         int next_partition = 4;
    219         if (current_stops.x > offset) {
    220             next_partition = 0;
    221             next_offset = current_stops.x;
    222         } else if (current_stops.y > offset) {
    223             next_partition = 1;
    224             prev_offset = current_stops.x;
    225             next_offset = current_stops.y;
    226         } else if (current_stops.z > offset) {
    227             next_partition = 2;
    228             prev_offset = current_stops.y;
    229             next_offset = current_stops.z;
    230         } else if (current_stops.w > offset) {
    231             next_partition = 3;
    232             prev_offset = current_stops.z;
    233             next_offset = current_stops.w;
    234         } else {
    235             prev_offset = current_stops.w;
    236         }
    237 
    238         index += next_partition * index_stride;
    239 
    240         if (index_stride == 1) {
    241             // If the index stride is 1, we visited a leaf,
    242             // we are done.
    243             break;
    244         }
    245 
    246         index_stride /= 5;
    247         level_base_addr += level_stride;
    248         level_stride *= 5;
    249         offset_in_level = offset_in_level * 5 + next_partition;
    250 
    251         // Fetch new offsets for the next iteration.
    252         current_stops = fetch_from_gpu_buffer_1f(level_base_addr + offset_in_level);
    253     }
    254 
    255     // If we are before the first gradient stop, next_offset and prev_offset
    256     // will be equal, in which case we want the contribution of the first stop, so
    257     // the interpolaiton factor remains zero.
    258     float d = next_offset - prev_offset;
    259     float factor = 0.0;
    260     if (index >= count) {
    261         // The current offset is after the last gradient stop.
    262         factor = 1.0;
    263     } else if (d > 0.0) {
    264         factor = clamp((offset - prev_offset) / d, 0.0, 1.0);
    265     }
    266 
    267     // TODO: This is clamp(index, 1, count - 1) but swgl
    268     // does not have the clamp overload for ints.
    269     if (index < 1) {
    270         index = 1;
    271     } else if (index > count - 1) {
    272         index = count - 1;
    273     }
    274     int color_pair_address = colors_addr + index - 1;
    275     vec4 color_pair[2] = fetch_from_gpu_buffer_2f(color_pair_address);
    276 
    277     return mix(color_pair[0], color_pair[1], factor);
    278 }
    279 
    280 // A fast path for sampling no more than two gradient stops.
    281 //
    282 // This version reads data from varyings instead of the gpu_buffer.
    283 vec4 sample_gradient_stops_fast(float offset) {
    284     float d = v_stop_offsets.y - v_stop_offsets.x;
    285     float factor = 0.0;
    286 
    287     if (offset < v_stop_offsets.x) {
    288         factor = 0.0;
    289         d = 1.0;
    290     } else if (offset > v_stop_offsets.y) {
    291         factor = 1.0;
    292         d = 1.0;
    293     } else if (d > 0.0) {
    294         factor = clamp((offset - v_stop_offsets.x) / d, 0.0, 1.0);
    295     }
    296 
    297     return mix(v_color0, v_color1, factor);
    298 }
    299 
    300 float linear_gradient_fragment() {
    301     vec2 pos = v_interpolated_data.xy;
    302     vec2 scale_dir = v_flat_data.xy;
    303     float start_offset = v_flat_data.z;
    304 
    305     // Project position onto a direction vector to compute offset.
    306     return dot(pos, scale_dir) - start_offset;
    307 }
    308 
    309 float radial_gradient_fragment() {
    310     // Solve for t in length(pos) = start_radius + t * rd
    311     vec2 pos = v_interpolated_data.xy;
    312     float start_radius = v_flat_data.x;
    313 
    314     return length(pos) - start_radius;
    315 }
    316 
    317 float conic_gradient_fragment() {
    318     vec2 current_dir = v_interpolated_data.xy;
    319     float start_offset = v_interpolated_data.z;
    320     float offset_scale = v_interpolated_data.w;
    321     float angle = v_flat_data.x;
    322 
    323     float current_angle = approx_atan2(current_dir.y, current_dir.x) + angle;
    324     return fract(current_angle / (2.0 * PI)) * offset_scale - start_offset;
    325 }
    326 
    327 vec4 pattern_fragment(vec4 color) {
    328 
    329     float offset = 0.0;
    330     switch (v_gradient_header.x) {
    331         case GRADIENT_KIND_LINEAR: {
    332             offset = linear_gradient_fragment();
    333             break;
    334         }
    335         case GRADIENT_KIND_RADIAL: {
    336             offset = radial_gradient_fragment();
    337             break;
    338         }
    339         case GRADIENT_KIND_CONIC: {
    340             offset = conic_gradient_fragment();
    341             break;
    342         }
    343         default: {
    344             break;
    345         }
    346     }
    347 
    348     offset = apply_extend_mode(offset);
    349 
    350     int stop_count = v_gradient_header.y;
    351     if (stop_count <= 2) {
    352         color *= sample_gradient_stops_fast(offset);
    353     } else {
    354         color *= sample_gradient_stops_tree(offset);
    355     }
    356 
    357     return dither(color);
    358 }
    359 
    360 #if defined(SWGL_DRAW_SPAN)
    361 void swgl_drawSpanRGBA8() {
    362     int kind = v_gradient_header.x;
    363     if (kind != GRADIENT_KIND_LINEAR && kind != GRADIENT_KIND_RADIAL) {
    364         return;
    365     }
    366 
    367     int stop_count = v_gradient_header.y;
    368     int colors_address = v_gradient_header.w;
    369     int colors_addr = swgl_validateGradientFromStops(sGpuBufferF, get_gpu_buffer_uv(colors_address),
    370                                                      stop_count);
    371     if (colors_addr < 0) {
    372         // The gradient is invalid, this should not happen. We can't fall back to
    373         // the regular shader code because it expects the gradient stop offsets
    374         // to be laid out for a tree traversal but we laid them out linearly because
    375         // that's the fastest way for the span shader.
    376         // Replace the gradient with a pink solid color.
    377         swgl_commitSolidRGBA8(vec4(1.0, 0.0, 1.0, 1.0));
    378         return;
    379     }
    380 
    381     int offsets_addr = colors_addr + stop_count * 4;
    382     vec2 pos = v_interpolated_data.xy;
    383     bool repeat = v_gradient_header.z != 0.0;
    384 
    385     if (kind == GRADIENT_KIND_LINEAR) {
    386         vec2 scale_dir = v_flat_data.xy;
    387         float start_offset = v_flat_data.z;
    388 
    389 #ifdef WR_FEATURE_DITHERING
    390         swgl_commitDitheredLinearGradientFromStopsRGBA8(sGpuBufferF, offsets_addr, colors_addr,
    391             stop_count, repeat, pos, scale_dir, start_offset, gl_FragCoord);
    392 #else
    393         swgl_commitLinearGradientFromStopsRGBA8(sGpuBufferF, offsets_addr, colors_addr,
    394             stop_count, repeat, pos, scale_dir, start_offset);
    395 #endif
    396     } else if (kind == GRADIENT_KIND_RADIAL) {
    397       float start_radius = v_flat_data.x;
    398 
    399 #ifdef WR_FEATURE_DITHERING
    400       swgl_commitDitheredRadialGradientFromStopsRGBA8(sGpuBufferF, offsets_addr, colors_addr,
    401           stop_count, repeat, pos, start_radius);
    402 #else
    403       swgl_commitRadialGradientFromStopsRGBA8(sGpuBufferF, offsets_addr, colors_addr,
    404           stop_count, repeat, pos, start_radius);
    405 #endif
    406     }
    407 }
    408 #endif
    409 
    410 #endif