shaders.hlsl

  1cbuffer GlobalParams: register(b0) {
  2    float2 global_viewport_size;
  3    uint2 _global_pad;
  4};
  5
  6Texture2D<float4> t_sprite: register(t0);
  7SamplerState s_sprite: register(s0);
  8
  9struct Bounds {
 10    float2 origin;
 11    float2 size;
 12};
 13
 14struct Corners {
 15    float top_left;
 16    float top_right;
 17    float bottom_right;
 18    float bottom_left;
 19};
 20
 21struct Edges {
 22    float top;
 23    float right;
 24    float bottom;
 25    float left;
 26};
 27
 28struct Hsla {
 29    float h;
 30    float s;
 31    float l;
 32    float a;
 33};
 34
 35struct LinearColorStop {
 36    Hsla color;
 37    float percentage;
 38};
 39
 40struct Background {
 41    // 0u is Solid
 42    // 1u is LinearGradient
 43    uint tag;
 44    // 0u is sRGB linear color
 45    // 1u is Oklab color
 46    uint color_space;
 47    Hsla solid;
 48    float angle;
 49    LinearColorStop colors[2];
 50    uint pad;
 51};
 52
 53struct GradientColor {
 54  float4 solid;
 55  float4 color0;
 56  float4 color1;
 57};
 58
 59struct AtlasTextureId {
 60    uint index;
 61    uint kind;
 62};
 63
 64struct AtlasBounds {
 65    int2 origin;
 66    int2 size;
 67};
 68
 69struct AtlasTile {
 70    AtlasTextureId texture_id;
 71    uint tile_id;
 72    uint padding;
 73    AtlasBounds bounds;
 74};
 75
 76struct TransformationMatrix {
 77    float2x2 rotation_scale;
 78    float2 translation;
 79};
 80
 81static const float M_PI_F = 3.141592653f;
 82static const float3 GRAYSCALE_FACTORS = float3(0.2126f, 0.7152f, 0.0722f);
 83
 84float4 to_device_position(float2 unit_vertex, Bounds bounds) {
 85    float2 position = unit_vertex * bounds.size + bounds.origin;
 86    float2 device_position = position / global_viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0);
 87    return float4(device_position, 0., 1.);
 88}
 89
 90float4 distance_from_clip_rect(float2 unit_vertex, Bounds bounds, Bounds clip_bounds) {
 91    float2 position = unit_vertex * bounds.size + bounds.origin;
 92    return float4(position.x - clip_bounds.origin.x,
 93                    clip_bounds.origin.x + clip_bounds.size.x - position.x,
 94                    position.y - clip_bounds.origin.y,
 95                    clip_bounds.origin.y + clip_bounds.size.y - position.y);
 96}
 97
 98// Convert linear RGB to sRGB
 99float3 linear_to_srgb(float3 color) {
100    return pow(color, float3(2.2));
101}
102
103// Convert sRGB to linear RGB
104float3 srgb_to_linear(float3 color) {
105    return pow(color, float3(1.0 / 2.2));
106}
107
108/// Hsla to linear RGBA conversion.
109float4 hsla_to_rgba(Hsla hsla) {
110    float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
111    float s = hsla.s;
112    float l = hsla.l;
113    float a = hsla.a;
114
115    float c = (1.0 - abs(2.0 * l - 1.0)) * s;
116    float x = c * (1.0 - abs(fmod(h, 2.0) - 1.0));
117    float m = l - c / 2.0;
118
119    float r = 0.0;
120    float g = 0.0;
121    float b = 0.0;
122
123    if (h >= 0.0 && h < 1.0) {
124        r = c;
125        g = x;
126        b = 0.0;
127    } else if (h >= 1.0 && h < 2.0) {
128        r = x;
129        g = c;
130        b = 0.0;
131    } else if (h >= 2.0 && h < 3.0) {
132        r = 0.0;
133        g = c;
134        b = x;
135    } else if (h >= 3.0 && h < 4.0) {
136        r = 0.0;
137        g = x;
138        b = c;
139    } else if (h >= 4.0 && h < 5.0) {
140        r = x;
141        g = 0.0;
142        b = c;
143    } else {
144        r = c;
145        g = 0.0;
146        b = x;
147    }
148
149    float4 rgba;
150    rgba.x = (r + m);
151    rgba.y = (g + m);
152    rgba.z = (b + m);
153    rgba.w = a;
154    return rgba;
155}
156
157// Converts a sRGB color to the Oklab color space.
158// Reference: https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
159float4 srgb_to_oklab(float4 color) {
160    // Convert non-linear sRGB to linear sRGB
161    color = float4(srgb_to_linear(color.rgb), color.a);
162
163    float l = 0.4122214708 * color.r + 0.5363325363 * color.g + 0.0514459929 * color.b;
164    float m = 0.2119034982 * color.r + 0.6806995451 * color.g + 0.1073969566 * color.b;
165    float s = 0.0883024619 * color.r + 0.2817188376 * color.g + 0.6299787005 * color.b;
166
167    float l_ = pow(l, 1.0/3.0);
168    float m_ = pow(m, 1.0/3.0);
169    float s_ = pow(s, 1.0/3.0);
170
171    return float4(
172        0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
173        1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
174        0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
175        color.a
176    );
177}
178
179// Converts an Oklab color to the sRGB color space.
180float4 oklab_to_srgb(float4 color) {
181    float l_ = color.r + 0.3963377774 * color.g + 0.2158037573 * color.b;
182    float m_ = color.r - 0.1055613458 * color.g - 0.0638541728 * color.b;
183    float s_ = color.r - 0.0894841775 * color.g - 1.2914855480 * color.b;
184
185    float l = l_ * l_ * l_;
186    float m = m_ * m_ * m_;
187    float s = s_ * s_ * s_;
188
189    float3 linear_rgb = float3(
190        4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
191        -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
192        -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
193    );
194
195    // Convert linear sRGB to non-linear sRGB
196    return float4(linear_to_srgb(linear_rgb), color.a);
197}
198
199// This approximates the error function, needed for the gaussian integral
200float2 erf(float2 x) {
201    float2 s = sign(x);
202    float2 a = abs(x);
203    x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
204    x *= x;
205    return s - s / (x * x);
206}
207
208float blur_along_x(float x, float y, float sigma, float corner, float2 half_size) {
209    float delta = min(half_size.y - corner - abs(y), 0.);
210    float curved = half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
211    float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
212    return integral.y - integral.x;
213}
214
215// A standard gaussian function, used for weighting samples
216float gaussian(float x, float sigma) {
217    return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
218}
219
220float4 over(float4 below, float4 above) {
221    float4 result;
222    float alpha = above.a + below.a * (1.0 - above.a);
223    result.rgb = (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha;
224    result.a = alpha;
225    return result;
226}
227
228float2 to_tile_position(float2 unit_vertex, AtlasTile tile) {
229    float2 atlas_size;
230    t_sprite.GetDimensions(atlas_size.x, atlas_size.y);
231    return (float2(tile.bounds.origin) + unit_vertex * float2(tile.bounds.size)) / atlas_size;
232}
233
234float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds, 
235                                      TransformationMatrix transformation) {
236    float2 position = unit_vertex * bounds.size + bounds.origin;
237    float2 transformed = mul(position, transformation.rotation_scale) + transformation.translation;
238    float2 device_position = transformed / global_viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0);
239    return float4(device_position, 0.0, 1.0);
240}
241
242float quad_sdf(float2 pt, Bounds bounds, Corners corner_radii) {
243    float2 half_size = bounds.size / 2.;
244    float2 center = bounds.origin + half_size;
245    float2 center_to_point = pt - center;
246    float corner_radius;
247    if (center_to_point.x < 0.) {
248        if (center_to_point.y < 0.) {
249            corner_radius = corner_radii.top_left;
250        } else {
251            corner_radius = corner_radii.bottom_left;
252        }
253    } else {
254        if (center_to_point.y < 0.) {
255            corner_radius = corner_radii.top_right;
256        } else {
257            corner_radius = corner_radii.bottom_right;
258        }
259    }
260
261    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
262    float distance =
263        length(max(0., rounded_edge_to_point)) +
264        min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
265        corner_radius;
266
267    return distance;
268}
269
270GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, Hsla color0, Hsla color1) {
271    GradientColor res;
272    if (tag == 0) {
273        res.solid = hsla_to_rgba(solid);
274    } else if (tag == 1) {
275        res.color0 = hsla_to_rgba(color0);
276        res.color1 = hsla_to_rgba(color1);
277
278        // Prepare color space in vertex for avoid conversion
279        // in fragment shader for performance reasons
280        if (color_space == 1) {
281        // Oklab
282        res.color0 = srgb_to_oklab(res.color0);
283        res.color1 = srgb_to_oklab(res.color1);
284        }
285    }
286
287    return res;
288}
289
290float4 gradient_color(Background background,
291                      float2 position,
292                      Bounds bounds,
293                      float4 solid_color, float4 color0, float4 color1) {
294    float4 color;
295
296    switch (background.tag) {
297        case 0:
298            color = solid_color;
299            break;
300        case 1: {
301            // -90 degrees to match the CSS gradient angle.
302            float radians = (fmod(background.angle, 360.0) - 90.0) * (M_PI_F / 180.0);
303            float2 direction = float2(cos(radians), sin(radians));
304
305            // Expand the short side to be the same as the long side
306            if (bounds.size.x > bounds.size.y) {
307                direction.y *= bounds.size.y / bounds.size.x;
308            } else {
309                direction.x *=  bounds.size.x / bounds.size.y;
310            }
311
312            // Get the t value for the linear gradient with the color stop percentages.
313            float2 half_size = float2(bounds.size.x, bounds.size.y) / 2.;
314            float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
315            float2 center_to_point = position - center;
316            float t = dot(center_to_point, direction) / length(direction);
317            // Check the direct to determine the use x or y
318            if (abs(direction.x) > abs(direction.y)) {
319                t = (t + half_size.x) / bounds.size.x;
320            } else {
321                t = (t + half_size.y) / bounds.size.y;
322            }
323
324            // Adjust t based on the stop percentages
325            t = (t - background.colors[0].percentage)
326                / (background.colors[1].percentage
327                - background.colors[0].percentage);
328            t = clamp(t, 0.0, 1.0);
329
330            switch (background.color_space) {
331                case 0:
332                    color = lerp(color0, color1, t);
333                    break;
334                case 1: {
335                    float4 oklab_color = lerp(color0, color1, t);
336                    color = oklab_to_srgb(oklab_color);
337                    break;
338                }
339            }
340            break;
341        }
342    }
343
344    return color;
345}
346
347/*
348**
349**              Shadows
350**
351*/
352
353struct ShadowVertexOutput {
354    float4 position: SV_Position;
355    float4 color: COLOR;
356    uint shadow_id: FLAT;
357    float4 clip_distance: SV_ClipDistance;
358};
359
360struct ShadowFragmentInput {
361  float4 position: SV_Position;
362  float4 color: COLOR;
363  uint shadow_id: FLAT;
364};
365
366struct Shadow {
367    uint order;
368    float blur_radius;
369    Bounds bounds;
370    Corners corner_radii;
371    Bounds content_mask;
372    Hsla color;
373};
374
375StructuredBuffer<Shadow> shadows: register(t1);
376
377ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV_InstanceID) {
378    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
379    Shadow shadow = shadows[shadow_id];
380
381    float margin = 3.0 * shadow.blur_radius;
382    Bounds bounds = shadow.bounds;
383    bounds.origin -= margin;
384    bounds.size += 2.0 * margin;
385
386    float4 device_position = to_device_position(unit_vertex, bounds);
387    float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask);
388    float4 color = hsla_to_rgba(shadow.color);
389
390    ShadowVertexOutput output;
391    output.position = device_position;
392    output.color = color;
393    output.shadow_id = shadow_id;
394    output.clip_distance = clip_distance;
395
396    return output;
397}
398
399float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
400    Shadow shadow = shadows[input.shadow_id];
401
402    float2 half_size = shadow.bounds.size / 2.;
403    float2 center = shadow.bounds.origin + half_size;
404    float2 point0 = input.position.xy - center;
405    float corner_radius;
406    if (point0.x < 0.) {
407        if (point0.y < 0.) {
408            corner_radius = shadow.corner_radii.top_left;
409        } else {
410            corner_radius = shadow.corner_radii.bottom_left;
411        }
412    } else {
413        if (point0.y < 0.) {
414            corner_radius = shadow.corner_radii.top_right;
415        } else {
416            corner_radius = shadow.corner_radii.bottom_right;
417        }
418    }
419
420    // The signal is only non-zero in a limited range, so don't waste samples
421    float low = point0.y - half_size.y;
422    float high = point0.y + half_size.y;
423    float start = clamp(-3. * shadow.blur_radius, low, high);
424    float end = clamp(3. * shadow.blur_radius, low, high);
425
426    // Accumulate samples (we can get away with surprisingly few samples)
427    float step = (end - start) / 4.;
428    float y = start + step * 0.5;
429    float alpha = 0.;
430    for (int i = 0; i < 4; i++) {
431        alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius,
432                            corner_radius, half_size) *
433                gaussian(y, shadow.blur_radius) * step;
434        y += step;
435    }
436
437    return input.color * float4(1., 1., 1., alpha);
438}
439
440/*
441**
442**              Quads
443**
444*/
445
446struct Quad {
447    uint order;
448    uint pad;
449    Bounds bounds;
450    Bounds content_mask;
451    Background background;
452    Hsla border_color;
453    Corners corner_radii;
454    Edges border_widths;
455};
456
457struct QuadVertexOutput {
458    float4 position: SV_Position;
459    // float4 border_color: COLOR0;
460    float4 border_color: FLAT;
461    uint quad_id: FLAT;
462    float4 background_solid: FLAT;
463    float4 background_color0: FLAT;
464    float4 background_color1: FLAT;
465    float4 clip_distance: SV_ClipDistance;
466};
467
468struct QuadFragmentInput {
469    uint quad_id: FLAT;
470    float4 position: SV_Position;
471    float4 border_color: FLAT;
472    float4 background_solid: FLAT;
473    float4 background_color0: FLAT;
474    float4 background_color1: FLAT;
475};
476
477StructuredBuffer<Quad> quads: register(t1);
478
479QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_InstanceID) {
480    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
481    Quad quad = quads[quad_id];
482    float4 device_position = to_device_position(unit_vertex, quad.bounds);
483    float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
484    float4 border_color = hsla_to_rgba(quad.border_color);
485
486    GradientColor gradient = prepare_gradient_color(
487        quad.background.tag,
488        quad.background.color_space,
489        quad.background.solid,
490        quad.background.colors[0].color,
491        quad.background.colors[1].color
492    );
493
494    QuadVertexOutput output;
495    output.position = device_position;
496    output.border_color = border_color;
497    output.quad_id = quad_id;
498    output.background_solid = gradient.solid;
499    output.background_color0 = gradient.color0;
500    output.background_color1 = gradient.color1;
501    output.clip_distance = clip_distance;
502    return output;
503}
504
505float4 quad_fragment(QuadFragmentInput input): SV_Target {
506    Quad quad = quads[input.quad_id];
507    float2 half_size = quad.bounds.size / 2.;
508    float2 center = quad.bounds.origin + half_size;
509    float2 center_to_point = input.position.xy - center;
510    float4 color = gradient_color(quad.background, input.position.xy, quad.bounds,
511    input.background_solid, input.background_color0, input.background_color1);
512
513    // Fast path when the quad is not rounded and doesn't have any border.
514    if (quad.corner_radii.top_left == 0. && quad.corner_radii.bottom_left == 0. &&
515        quad.corner_radii.top_right == 0. &&
516        quad.corner_radii.bottom_right == 0. && quad.border_widths.top == 0. &&
517        quad.border_widths.left == 0. && quad.border_widths.right == 0. &&
518        quad.border_widths.bottom == 0.) {
519        return color;
520    }
521
522    float corner_radius;
523    if (center_to_point.x < 0.) {
524        if (center_to_point.y < 0.) {
525            corner_radius = quad.corner_radii.top_left;
526        } else {
527            corner_radius = quad.corner_radii.bottom_left;
528        }
529    } else {
530        if (center_to_point.y < 0.) {
531            corner_radius = quad.corner_radii.top_right;
532        } else {
533            corner_radius = quad.corner_radii.bottom_right;
534        }
535    }
536
537    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
538    float distance =
539        length(max(0., rounded_edge_to_point)) +
540        min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
541        corner_radius;
542
543    float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
544                                                    : quad.border_widths.right;
545    float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
546                                                        : quad.border_widths.bottom;
547    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
548    float2 point_to_inset_corner = abs(center_to_point) - inset_size;
549    float border_width;
550    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
551        border_width = 0.;
552    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
553        border_width = horizontal_border;
554    } else {
555        border_width = vertical_border;
556    }
557
558    if (border_width != 0.) {
559        float inset_distance = distance + border_width;
560        // Blend the border on top of the background and then linearly interpolate
561        // between the two as we slide inside the background.
562        float4 blended_border = over(color, input.border_color);
563        color = lerp(blended_border, color, saturate(0.5 - inset_distance));
564    }
565
566    return color * float4(1., 1., 1., saturate(0.5 - distance));
567}
568
569/*
570**
571**              Path raster
572**
573*/
574
575struct PathVertex {
576    float2 xy_position;
577    float2 st_position;
578    Bounds content_mask;
579};
580
581struct PathRasterizationOutput {
582    float4 position: SV_Position;
583    float2 st_position: TEXCOORD0;
584    float4 clip_distances: SV_ClipDistance;
585};
586
587struct PathRasterizationInput {
588    float4 position: SV_Position;
589    float2 st_position: TEXCOORD0;
590};
591
592StructuredBuffer<PathVertex> path_vertices: register(t1);
593
594PathRasterizationOutput path_rasterization_vertex(uint vertex_id: SV_VertexID) {
595    PathVertex vertex = path_vertices[vertex_id];
596    PathRasterizationOutput output;
597    float2 device_position = vertex.xy_position / global_viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0);
598    float2 tl = vertex.xy_position - vertex.content_mask.origin;
599    float2 br = vertex.content_mask.origin + vertex.content_mask.size - vertex.xy_position;
600
601    output.position = float4(device_position, 0.0, 1.0);
602    output.st_position = vertex.st_position;
603    output.clip_distances = float4(tl.x, br.x, tl.y, br.y);
604    return output;
605}
606
607float4 path_rasterization_fragment(PathRasterizationInput input): SV_Target {
608    float2 dx = ddx(input.st_position);
609    float2 dy = ddy(input.st_position);
610    float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
611                            (2. * input.st_position.x) * dy.x - dy.y);
612    float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
613    float distance = f / length(gradient);
614    float alpha = saturate(0.5 - distance);
615    return float4(alpha, 0., 0., 1.);
616}
617
618/*
619**
620**              Paths
621**
622*/
623
624struct PathSprite {
625    Bounds bounds;
626    Background color;
627    AtlasTile tile;
628};
629
630struct PathVertexOutput {
631    float4 position: SV_Position;
632    float2 tile_position: POSITION1;
633    uint sprite_id: FLAT;
634    float4 solid_color: FLAT;
635    float4 color0: FLAT;
636    float4 color1: FLAT;
637};
638
639StructuredBuffer<PathSprite> path_sprites: register(t1);
640
641PathVertexOutput paths_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
642    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
643    PathSprite sprite = path_sprites[instance_id];
644    // Don't apply content mask because it was already accounted for when rasterizing the path.
645    PathVertexOutput output;
646    output.position = to_device_position(unit_vertex, sprite.bounds);
647    output.tile_position = to_tile_position(unit_vertex, sprite.tile);
648    output.sprite_id = instance_id;
649
650    GradientColor gradient = prepare_gradient_color(
651        sprite.color.tag,
652        sprite.color.color_space,
653        sprite.color.solid,
654        sprite.color.colors[0].color,
655        sprite.color.colors[1].color
656    );
657
658    output.solid_color = gradient.solid;
659    output.color0 = gradient.color0;
660    output.color1 = gradient.color1;
661    return output;
662}
663
664float4 paths_fragment(PathVertexOutput input): SV_Target {
665    float sample = t_sprite.Sample(s_sprite, input.tile_position).r;
666    float mask = 1.0 - abs(1.0 - sample % 2.0);
667    PathSprite sprite = path_sprites[input.sprite_id];
668    Background background = sprite.color;
669    float4 color = gradient_color(background, input.position.xy, sprite.bounds,
670        input.solid_color, input.color0, input.color1);
671    color.a *= mask;
672    return color;
673}
674
675/*
676**
677**              Underlines
678**
679*/
680
681struct Underline {
682    uint order;
683    uint pad;
684    Bounds bounds;
685    Bounds content_mask;
686    Hsla color;
687    float thickness;
688    uint wavy;
689};
690
691struct UnderlineVertexOutput {
692  float4 position: SV_Position;
693  float4 color: COLOR;
694  uint underline_id: FLAT;
695  float4 clip_distance: SV_ClipDistance;
696};
697
698struct UnderlineFragmentInput {
699  float4 position: SV_Position;
700  float4 color: COLOR;
701  uint underline_id: FLAT;
702};
703
704StructuredBuffer<Underline> underlines: register(t1);
705
706UnderlineVertexOutput underline_vertex(uint vertex_id: SV_VertexID, uint underline_id: SV_InstanceID) {
707    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
708    Underline underline = underlines[underline_id];
709    float4 device_position = to_device_position(unit_vertex, underline.bounds);
710    float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds, 
711                                                    underline.content_mask);
712    float4 color = hsla_to_rgba(underline.color);
713
714    UnderlineVertexOutput output;
715    output.position = device_position;
716    output.color = color;
717    output.underline_id = underline_id;
718    output.clip_distance = clip_distance;
719    return output;
720}
721
722float4 underline_fragment(UnderlineFragmentInput input): SV_Target {
723    Underline underline = underlines[input.underline_id];
724    if (underline.wavy) {
725        float half_thickness = underline.thickness * 0.5;
726        float2 origin =
727            float2(underline.bounds.origin.x, underline.bounds.origin.y);
728        float2 st = ((input.position.xy - origin) / underline.bounds.size.y) -
729                    float2(0., 0.5);
730        float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
731        float amplitude = 1. / (2. * underline.thickness);
732        float sine = sin(st.x * frequency) * amplitude;
733        float dSine = cos(st.x * frequency) * amplitude * frequency;
734        float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
735        float distance_in_pixels = distance * underline.bounds.size.y;
736        float distance_from_top_border = distance_in_pixels - half_thickness;
737        float distance_from_bottom_border = distance_in_pixels + half_thickness;
738        float alpha = saturate(
739            0.5 - max(-distance_from_bottom_border, distance_from_top_border));
740        return input.color * float4(1., 1., 1., alpha);
741    } else {
742        return input.color;
743    }
744}
745
746/*
747**
748**              Monochrome sprites
749**
750*/
751
752struct MonochromeSprite {
753    uint order;
754    uint pad;
755    Bounds bounds;
756    Bounds content_mask;
757    Hsla color;
758    AtlasTile tile;
759    TransformationMatrix transformation;
760};
761
762struct MonochromeSpriteVertexOutput {
763    float4 position: SV_Position;
764    float2 tile_position: POSITION;
765    float4 color: COLOR;
766    float4 clip_distance: SV_ClipDistance;
767};
768
769struct MonochromeSpriteFragmentInput {
770    float4 position: SV_Position;
771    float2 tile_position: POSITION;
772    float4 color: COLOR;
773};
774
775StructuredBuffer<MonochromeSprite> mono_sprites: register(t1);
776
777MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
778    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
779    MonochromeSprite sprite = mono_sprites[sprite_id];
780    float4 device_position =
781        to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
782    float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
783    float2 tile_position = to_tile_position(unit_vertex, sprite.tile);
784    float4 color = hsla_to_rgba(sprite.color);
785
786    MonochromeSpriteVertexOutput output;
787    output.position = device_position;
788    output.tile_position = tile_position;
789    output.color = color;
790    output.clip_distance = clip_distance;
791    return output;
792}
793
794float4 monochrome_sprite_fragment(MonochromeSpriteFragmentInput input): SV_Target {
795    float4 sample = t_sprite.Sample(s_sprite, input.tile_position);
796    float4 color = input.color;
797    color.a *= sample.a;
798    return color;
799}
800
801/*
802**
803**              Polychrome sprites
804**
805*/
806
807struct PolychromeSprite {
808    uint order;
809    uint grayscale;
810    Bounds bounds;
811    Bounds content_mask;
812    Corners corner_radii;
813    AtlasTile tile;
814};
815
816struct PolychromeSpriteVertexOutput {
817    float4 position: SV_Position;
818    float2 tile_position: POSITION;
819    uint sprite_id: FLAT;
820    float4 clip_distance: SV_ClipDistance;
821};
822
823struct PolychromeSpriteFragmentInput {
824    float4 position: SV_Position;
825    float2 tile_position: POSITION;
826    uint sprite_id: FLAT;
827};
828
829StructuredBuffer<PolychromeSprite> poly_sprites: register(t1);
830
831PolychromeSpriteVertexOutput polychrome_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
832    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
833    PolychromeSprite sprite = poly_sprites[sprite_id];
834    float4 device_position = to_device_position(unit_vertex, sprite.bounds);
835    float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
836                                                    sprite.content_mask);
837    float2 tile_position = to_tile_position(unit_vertex, sprite.tile);
838
839    PolychromeSpriteVertexOutput output;
840    output.position = device_position;
841    output.tile_position = tile_position;
842    output.sprite_id = sprite_id;
843    output.clip_distance = clip_distance;
844    return output;
845}
846
847float4 polychrome_sprite_fragment(PolychromeSpriteFragmentInput input): SV_Target {
848    PolychromeSprite sprite = poly_sprites[input.sprite_id];
849    float4 sample = t_sprite.Sample(s_sprite, input.tile_position);
850    float distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
851
852    float4 color = sample;
853    if ((sprite.grayscale & 0xFFu) != 0u) {
854        float3 grayscale = dot(color.rgb, GRAYSCALE_FACTORS);
855        color = float4(grayscale, sample.a);
856    }
857    color.a *= saturate(0.5 - distance);
858    return color;
859}