Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
/// This shader renders any kind of css gradents in a color or alpha target.
#include ps_quad
#include dithering
#define PI 3.141592653589793
#define GRADIENT_KIND_LINEAR 0
#define GRADIENT_KIND_RADIAL 1
#define GRADIENT_KIND_CONIC 2
// All of the integer varyings are packed into this header (see decode_gradient_header).
flat varying highp ivec4 v_gradient_header;
// Gradient-specific varying parameters are packed into these two varyings.
varying highp vec4 v_interpolated_data;
flat varying highp vec4 v_flat_data;
// The first four stop offsets provided to the fragment shader to reduce the
// number of gpu buffer reads in the common case.
flat varying mediump vec4 v_stop_offsets;
// Two color stops provided via varyings, only used in the fast path with only
// two color stops.
flat varying mediump vec4 v_color0;
flat varying mediump vec4 v_color1;
#ifdef WR_VERTEX_SHADER
void linear_gradient_vertex(vec2 position, vec4 data0) {
vec2 p0 = data0.xy;
vec2 p1 = data0.zw;
vec2 dir = p1 - p0;
dir = dir / dot(dir, dir);
float offset = dot(p0, dir);
v_interpolated_data = vec4(position, 0.0, 0.0);
v_flat_data = vec4(dir, offset, 0.0);
}
void radial_gradient_vertex(vec2 position, vec4 data0, vec4 data1) {
vec2 center = data0.xy;
vec2 scale = data0.zw;
float start_radius = data1.x;
float end_radius = data1.y;
float xy_ratio = data1.z;
// Store 1/rd where rd = end_radius - start_radius
// If rd = 0, we can't get its reciprocal. Instead, just use a zero scale.
float rd = end_radius - start_radius;
float radius_scale = rd != 0.0 ? 1.0 / rd : 0.0;
// Transform all coordinates by the y scale so the
// fragment shader can work with circles
// v_pos is in a coordinate space relative to the task rect
// (so it is independent of the task origin).
start_radius = start_radius * radius_scale;
vec2 normalized_pos = (position * scale - center) * radius_scale;
normalized_pos.y *= xy_ratio;
v_interpolated_data = vec4(normalized_pos.x, normalized_pos.y, 0.0, 0.0);
v_flat_data = vec4(start_radius, 0.0, 0.0, 0.0);
}
void conic_gradient_vertex(vec2 position, vec4 data0, vec4 data1) {
vec2 center = data0.xy;
vec2 scale = data0.zw;
float start_offset = data1.x;
float end_offset = data1.y;
float angle = PI / 2.0 - data1.z;
// Store 1/d where d = end_offset - start_offset
// If d = 0, we can't get its reciprocal. Instead, just use a zero scale.
float d = end_offset - start_offset;
float offset_scale = d != 0.0 ? 1.0 / d : 0.0;
start_offset = start_offset * offset_scale;
vec2 dir = (position * scale - center);
v_interpolated_data = vec4(dir, start_offset, offset_scale);
v_flat_data = vec4(angle, 0.0, 0.0, 0.0);
}
ivec4 decode_gradient_header(int base_address, vec4 payload) {
int kind = int(payload.x);
int count = int(payload.y);
int extend_mode = int(payload.z);
int colors_address = base_address + 1;
return ivec4(
kind,
count,
extend_mode,
colors_address
);
}
void pattern_vertex(PrimitiveInfo info) {
int address = info.pattern_input.x;
// gradient[0..1] contains linear/radial/conic specific data,
// gradient[2] contains the header to interpret gradient stops.
vec4[3] gradient = fetch_from_gpu_buffer_3f(address);
ivec4 header = decode_gradient_header(address + 2, gradient[2]);
vec2 pos = info.local_pos - info.local_prim_rect.p0;
switch (header.x) {
case GRADIENT_KIND_LINEAR: {
linear_gradient_vertex(pos, gradient[0]);
break;
}
case GRADIENT_KIND_RADIAL: {
radial_gradient_vertex(pos, gradient[0], gradient[1]);
break;
}
case GRADIENT_KIND_CONIC: {
conic_gradient_vertex(pos, gradient[0], gradient[1]);
break;
}
default: {
// This should be dead code.
v_interpolated_data = vec4(0.0);
v_flat_data = vec4(0.0);
break;
}
}
int count = header.y;
int colors_addr = header.w;
int offsets_addrs = colors_addr + count;
v_stop_offsets = fetch_from_gpu_buffer_1f(offsets_addrs);
v_gradient_header = header;
if (count == 2) {
// Fast path: If we have only two color stops, pass them by varyings.
vec4[2] colors = fetch_from_gpu_buffer_2f(colors_addr);
v_color0 = colors[0];
v_color1 = colors[1];
}
}
#endif
#ifdef WR_FRAGMENT_SHADER
float approx_atan2(float y, float x) {
vec2 a = abs(vec2(x, y));
float slope = min(a.x, a.y) / max(a.x, a.y);
float s2 = slope * slope;
float r = ((-0.0464964749 * s2 + 0.15931422) * s2 - 0.327622764) * s2 * slope + slope;
r = if_then_else(float(a.y > a.x), 1.57079637 - r, r);
r = if_then_else(float(x < 0.0), 3.14159274 - r, r);
// To match atan2's behavior, -0.0 should count as negative and flip the sign of r.
// Does this matter in practice in the context of conic gradients?
r = y < 0.0 ? -r : r;
return r;
}
float apply_extend_mode(float offset) {
// Handle the repeat mode.
float mode = float(v_gradient_header.z);
offset -= floor(offset) * mode;
return offset;
}
// Sample the gradient using a sequence of gradient stops located at the provided
// addresses.
//
// See the comment above `prim_store::gradient::write_gpu_gradient_stops_tree` about
// the layout of the gradient data in the gpu buffer.
vec4 sample_gradient_stops_tree(float offset) {
int count = v_gradient_header.y;
int colors_addr = v_gradient_header.w;
// Address of the current level
int level_base_addr = colors_addr + count;
// Number of blocks of 4 indices for the current level.
// At the root, a single block is stored. Each level stores
// 5 times more blocks than the previous one.
int level_stride = 1;
// Relative address within the current level.
int offset_in_level = 0;
// Current gradient stop index.
int index = 0;
// The index distance between consecutive stop offsets at
// the current level. At the last level, the stride is 1.
// each has a 5 times more stride than the next (so the
// index stride starts high and is devided by 5 at each
// iteration).
int index_stride = 1;
while (index_stride * 5 <= count) {
index_stride *= 5;
}
// The offsets of the stops before and after the target offset.
// They will converge to the correct values as the tree is
// traversed.
float prev_offset = 1.0;
float next_offset = 0.0;
// First offsets are the root level.
vec4 current_stops = v_stop_offsets;
// This could be a while(true) since we are going to exit this loop
// its break statement, but we get miscompilations with while(true)
// so instead we put a dummy condition that always evaluates to true.
while (index_stride > 0) {
// Determine which of the five partitions (sub-trees)
// to take next.
int next_partition = 4;
if (current_stops.x > offset) {
next_partition = 0;
next_offset = current_stops.x;
} else if (current_stops.y > offset) {
next_partition = 1;
prev_offset = current_stops.x;
next_offset = current_stops.y;
} else if (current_stops.z > offset) {
next_partition = 2;
prev_offset = current_stops.y;
next_offset = current_stops.z;
} else if (current_stops.w > offset) {
next_partition = 3;
prev_offset = current_stops.z;
next_offset = current_stops.w;
} else {
prev_offset = current_stops.w;
}
index += next_partition * index_stride;
if (index_stride == 1) {
// If the index stride is 1, we visited a leaf,
// we are done.
break;
}
index_stride /= 5;
level_base_addr += level_stride;
level_stride *= 5;
offset_in_level = offset_in_level * 5 + next_partition;
// Fetch new offsets for the next iteration.
current_stops = fetch_from_gpu_buffer_1f(level_base_addr + offset_in_level);
}
// If we are before the first gradient stop, next_offset and prev_offset
// will be equal, in which case we want the contribution of the first stop, so
// the interpolaiton factor remains zero.
float d = next_offset - prev_offset;
float factor = 0.0;
if (index >= count) {
// The current offset is after the last gradient stop.
factor = 1.0;
} else if (d > 0.0) {
factor = clamp((offset - prev_offset) / d, 0.0, 1.0);
}
// TODO: This is clamp(index, 1, count - 1) but swgl
// does not have the clamp overload for ints.
if (index < 1) {
index = 1;
} else if (index > count - 1) {
index = count - 1;
}
int color_pair_address = colors_addr + index - 1;
vec4 color_pair[2] = fetch_from_gpu_buffer_2f(color_pair_address);
return mix(color_pair[0], color_pair[1], factor);
}
// A fast path for sampling no more than two gradient stops.
//
// This version reads data from varyings instead of the gpu_buffer.
vec4 sample_gradient_stops_fast(float offset) {
float d = v_stop_offsets.y - v_stop_offsets.x;
float factor = 0.0;
if (offset < v_stop_offsets.x) {
factor = 0.0;
d = 1.0;
} else if (offset > v_stop_offsets.y) {
factor = 1.0;
d = 1.0;
} else if (d > 0.0) {
factor = clamp((offset - v_stop_offsets.x) / d, 0.0, 1.0);
}
return mix(v_color0, v_color1, factor);
}
float linear_gradient_fragment() {
vec2 pos = v_interpolated_data.xy;
vec2 scale_dir = v_flat_data.xy;
float start_offset = v_flat_data.z;
// Project position onto a direction vector to compute offset.
return dot(pos, scale_dir) - start_offset;
}
float radial_gradient_fragment() {
// Solve for t in length(pos) = start_radius + t * rd
vec2 pos = v_interpolated_data.xy;
float start_radius = v_flat_data.x;
return length(pos) - start_radius;
}
float conic_gradient_fragment() {
vec2 current_dir = v_interpolated_data.xy;
float start_offset = v_interpolated_data.z;
float offset_scale = v_interpolated_data.w;
float angle = v_flat_data.x;
float current_angle = approx_atan2(current_dir.y, current_dir.x) + angle;
return fract(current_angle / (2.0 * PI)) * offset_scale - start_offset;
}
vec4 pattern_fragment(vec4 color) {
float offset = 0.0;
switch (v_gradient_header.x) {
case GRADIENT_KIND_LINEAR: {
offset = linear_gradient_fragment();
break;
}
case GRADIENT_KIND_RADIAL: {
offset = radial_gradient_fragment();
break;
}
case GRADIENT_KIND_CONIC: {
offset = conic_gradient_fragment();
break;
}
default: {
break;
}
}
offset = apply_extend_mode(offset);
int stop_count = v_gradient_header.y;
if (stop_count <= 2) {
color *= sample_gradient_stops_fast(offset);
} else {
color *= sample_gradient_stops_tree(offset);
}
return dither(color);
}
#if defined(SWGL_DRAW_SPAN)
void swgl_drawSpanRGBA8() {
if (v_gradient_header.x != GRADIENT_KIND_RADIAL) {
return;
}
int stop_count = v_gradient_header.y;
int colors_address = v_gradient_header.w;
int colors_addr = swgl_validateGradient(sGpuBufferF, get_gpu_buffer_uv(colors_address),
stop_count);
int offsets_addr = swgl_validateGradient(sGpuBufferF, get_gpu_buffer_uv(colors_address + stop_count),
stop_count);
if (offsets_addr < 0 || colors_addr < 0) {
// The gradient is invalid, this should not happen. We can't fall back to
// the regular shader code because it expects the gradient stop offsets
// to be laid out for a tree traversal but we laid them out linearly because
// that's the fastest way for the span shader.
// Replace the gradient with a pink solid color.
swgl_commitSolidRGBA8(vec4(1.0, 0.0, 1.0, 1.0));
return;
}
vec2 pos = v_interpolated_data.xy;
float start_radius = v_flat_data.x;
bool repeat = v_gradient_header.z != 0.0;
swgl_commitRadialGradientFromStopsRGBA8(sGpuBufferF, offsets_addr, colors_addr,
stop_count, repeat, pos, start_radius);
}
#endif
#endif