shaders.wgsl

  1struct Bounds {
  2    origin: vec2<f32>,
  3    size: vec2<f32>,
  4}
  5struct Corners {
  6    top_left: f32,
  7    top_right: f32,
  8    bottom_right: f32,
  9    bottom_left: f32,
 10}
 11struct Edges {
 12    top: f32,
 13    right: f32,
 14    bottom: f32,
 15    left: f32,
 16}
 17struct Hsla {
 18    h: f32,
 19    s: f32,
 20    l: f32,
 21    a: f32,
 22}
 23
 24struct Quad {
 25    view_id: vec2<u32>,
 26    layer_id: u32,
 27    order: u32,
 28    bounds: Bounds,
 29    content_mask: Bounds,
 30    background: Hsla,
 31    border_color: Hsla,
 32    corner_radii: Corners,
 33    border_widths: Edges,
 34}
 35
 36struct Globals {
 37    viewport_size: vec2<f32>,
 38    pad: vec2<u32>,
 39}
 40
 41var<uniform> globals: Globals;
 42var<storage, read> quads: array<Quad>;
 43
 44struct QuadsVarying {
 45    @builtin(position) position: vec4<f32>,
 46    @location(0) @interpolate(flat) background_color: vec4<f32>,
 47    @location(1) @interpolate(flat) border_color: vec4<f32>,
 48    @location(2) @interpolate(flat) quad_id: u32,
 49    //TODO: use `clip_distance` once Naga supports it
 50    @location(3) clip_distances: vec4<f32>,
 51}
 52
 53fn to_device_position(unit_vertex: vec2<f32>, bounds: Bounds) -> vec4<f32> {
 54    let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
 55    let device_position = position / globals.viewport_size * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0);
 56    return vec4<f32>(device_position, 0.0, 1.0);
 57}
 58
 59fn distance_from_clip_rect(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds: Bounds) -> vec4<f32> {
 60    let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
 61    let tl = position - clip_bounds.origin;
 62    let br = clip_bounds.origin + clip_bounds.size - position;
 63    return vec4<f32>(tl.x, br.x, tl.y, br.y);
 64}
 65
 66fn hsla_to_rgba(hsla: Hsla) -> vec4<f32> {
 67    let h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
 68    let s = hsla.s;
 69    let l = hsla.l;
 70    let a = hsla.a;
 71
 72    let c = (1.0 - abs(2.0 * l - 1.0)) * s;
 73    let x = c * (1.0 - abs(h % 2.0 - 1.0));
 74    let m = l - c / 2.0;
 75
 76    var color = vec4<f32>(m, m, m, a);
 77
 78    if (h >= 0.0 && h < 1.0) {
 79        color.r += c;
 80        color.g += x;
 81    } else if (h >= 1.0 && h < 2.0) {
 82        color.r += x;
 83        color.g += c;
 84    } else if (h >= 2.0 && h < 3.0) {
 85        color.g += c;
 86        color.b += x;
 87    } else if (h >= 3.0 && h < 4.0) {
 88        color.g += x;
 89        color.b += c;
 90    } else if (h >= 4.0 && h < 5.0) {
 91        color.r += x;
 92        color.b += c;
 93    } else {
 94        color.r += c;
 95        color.b += x;
 96    }
 97
 98    return color;
 99}
100
101fn over(below: vec4<f32>, above: vec4<f32>) -> vec4<f32> {
102  let alpha = above.a + below.a * (1.0 - above.a);
103  let color = (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha;
104  return vec4<f32>(color, alpha);
105}
106
107@vertex
108fn vs_quads(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> QuadsVarying {
109    let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
110    let quad = quads[instance_id];
111
112    var out = QuadsVarying();
113    out.position = to_device_position(unit_vertex, quad.bounds);
114    out.background_color = hsla_to_rgba(quad.background);
115    out.border_color = hsla_to_rgba(quad.border_color);
116    out.quad_id = instance_id;
117    out.clip_distances = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
118    return out;
119}
120
121@fragment
122fn fs_quads(input: QuadsVarying) -> @location(0) vec4<f32> {
123    // Alpha clip first, since we don't have `clip_distance`.
124    let min_distance = min(
125        min(input.clip_distances.x, input.clip_distances.y),
126        min(input.clip_distances.z, input.clip_distances.w)
127    );
128    if min_distance <= 0.0 {
129        return vec4<f32>(0.0);
130    }
131
132    let quad = quads[input.quad_id];
133    let half_size = quad.bounds.size / 2.0;
134    let center = quad.bounds.origin + half_size;
135    let center_to_point = input.position.xy - center;
136
137    var corner_radius = 0.0;
138    if (center_to_point.x < 0.0) {
139        if (center_to_point.y < 0.0) {
140            corner_radius = quad.corner_radii.top_left;
141        } else {
142            corner_radius = quad.corner_radii.bottom_left;
143        }
144    } else {
145        if (center_to_point.y < 0.) {
146            corner_radius = quad.corner_radii.top_right;
147        } else {
148            corner_radius = quad.corner_radii.bottom_right;
149        }
150    }
151
152    let rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
153    let distance =
154      length(max(vec2<f32>(0.0), rounded_edge_to_point)) +
155      min(0.0, max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
156      corner_radius;
157
158    let vertical_border = select(quad.border_widths.left, quad.border_widths.right, center_to_point.x > 0.0);
159    let horizontal_border = select(quad.border_widths.top, quad.border_widths.bottom, center_to_point.y > 0.0);
160    let inset_size = half_size - corner_radius - vec2<f32>(vertical_border, horizontal_border);
161    let point_to_inset_corner = abs(center_to_point) - inset_size;
162
163    var border_width = 0.0;
164    if (point_to_inset_corner.x < 0.0 && point_to_inset_corner.y < 0.0) {
165        border_width = 0.0;
166    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
167        border_width = horizontal_border;
168    } else {
169        border_width = vertical_border;
170    }
171
172    var color = input.background_color;
173    if (border_width > 0.0) {
174        let inset_distance = distance + border_width;
175        // Blend the border on top of the background and then linearly interpolate
176        // between the two as we slide inside the background.
177        let blended_border = over(input.background_color, input.border_color);
178        color = mix(blended_border, input.background_color,
179                    saturate(0.5 - inset_distance));
180    }
181
182    return color * vec4<f32>(1.0, 1.0, 1.0, saturate(0.5 - distance));
183}