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