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