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 AtlasTextureId {
 36    uint index;
 37    uint kind;
 38};
 39
 40struct AtlasBounds {
 41    int2 origin;
 42    int2 size;
 43};
 44
 45struct AtlasTile {
 46    AtlasTextureId texture_id;
 47    uint tile_id;
 48    uint padding;
 49    AtlasBounds bounds;
 50};
 51
 52struct TransformationMatrix {
 53    float2x2 rotation_scale;
 54    float2 translation;
 55};
 56
 57static const float M_PI_F = 3.141592653f;
 58static const float3 GRAYSCALE_FACTORS = float3(0.2126f, 0.7152f, 0.0722f);
 59
 60float4 to_device_position(float2 unit_vertex, Bounds bounds) {
 61    float2 position = unit_vertex * bounds.size + bounds.origin;
 62    float2 device_position = position / global_viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0);
 63    return float4(device_position, 0., 1.);
 64}
 65
 66float4 distance_from_clip_rect(float2 unit_vertex, Bounds bounds, Bounds clip_bounds) {
 67    float2 position = unit_vertex * bounds.size + bounds.origin;
 68    return float4(position.x - clip_bounds.origin.x,
 69                    clip_bounds.origin.x + clip_bounds.size.x - position.x,
 70                    position.y - clip_bounds.origin.y,
 71                    clip_bounds.origin.y + clip_bounds.size.y - position.y);
 72}
 73
 74float4 hsla_to_rgba(Hsla hsla) {
 75    float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
 76    float s = hsla.s;
 77    float l = hsla.l;
 78    float a = hsla.a;
 79
 80    float c = (1.0 - abs(2.0 * l - 1.0)) * s;
 81    float x = c * (1.0 - abs(fmod(h, 2.0) - 1.0));
 82    float m = l - c / 2.0;
 83
 84    float r = 0.0;
 85    float g = 0.0;
 86    float b = 0.0;
 87
 88    if (h >= 0.0 && h < 1.0) {
 89        r = c;
 90        g = x;
 91        b = 0.0;
 92    } else if (h >= 1.0 && h < 2.0) {
 93        r = x;
 94        g = c;
 95        b = 0.0;
 96    } else if (h >= 2.0 && h < 3.0) {
 97        r = 0.0;
 98        g = c;
 99        b = x;
100    } else if (h >= 3.0 && h < 4.0) {
101        r = 0.0;
102        g = x;
103        b = c;
104    } else if (h >= 4.0 && h < 5.0) {
105        r = x;
106        g = 0.0;
107        b = c;
108    } else {
109        r = c;
110        g = 0.0;
111        b = x;
112    }
113
114    float4 rgba;
115    rgba.x = (r + m);
116    rgba.y = (g + m);
117    rgba.z = (b + m);
118    rgba.w = a;
119    return rgba;
120}
121
122// This approximates the error function, needed for the gaussian integral
123float2 erf(float2 x) {
124    float2 s = sign(x);
125    float2 a = abs(x);
126    x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
127    x *= x;
128    return s - s / (x * x);
129}
130
131float blur_along_x(float x, float y, float sigma, float corner, float2 half_size) {
132    float delta = min(half_size.y - corner - abs(y), 0.);
133    float curved = half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
134    float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
135    return integral.y - integral.x;
136}
137
138// A standard gaussian function, used for weighting samples
139float gaussian(float x, float sigma) {
140    return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
141}
142
143float4 over(float4 below, float4 above) {
144    float4 result;
145    float alpha = above.a + below.a * (1.0 - above.a);
146    result.rgb = (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha;
147    result.a = alpha;
148    return result;
149}
150
151float2 to_tile_position(float2 unit_vertex, AtlasTile tile) {
152    float2 atlas_size;
153    t_sprite.GetDimensions(atlas_size.x, atlas_size.y);
154    return (float2(tile.bounds.origin) + unit_vertex * float2(tile.bounds.size)) / atlas_size;
155}
156
157float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds, 
158                                      TransformationMatrix transformation) {
159    float2 position = unit_vertex * bounds.size + bounds.origin;
160    float2 transformed = mul(position, transformation.rotation_scale) + transformation.translation;
161    float2 device_position = transformed / global_viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0);
162    return float4(device_position, 0.0, 1.0);
163}
164
165float quad_sdf(float2 pt, Bounds bounds, Corners corner_radii) {
166    float2 half_size = bounds.size / 2.;
167    float2 center = bounds.origin + half_size;
168    float2 center_to_point = pt - center;
169    float corner_radius;
170    if (center_to_point.x < 0.) {
171        if (center_to_point.y < 0.) {
172            corner_radius = corner_radii.top_left;
173        } else {
174            corner_radius = corner_radii.bottom_left;
175        }
176    } else {
177        if (center_to_point.y < 0.) {
178            corner_radius = corner_radii.top_right;
179        } else {
180            corner_radius = corner_radii.bottom_right;
181        }
182    }
183
184    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
185    float distance =
186        length(max(0., rounded_edge_to_point)) +
187        min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
188        corner_radius;
189
190    return distance;
191}
192
193/*
194**
195**              Shadows
196**
197*/
198
199struct ShadowVertexOutput {
200    float4 position: SV_Position;
201    float4 color: COLOR;
202    uint shadow_id: FLAT;
203    float4 clip_distance: SV_ClipDistance;
204};
205
206struct ShadowFragmentInput {
207  float4 position: SV_Position;
208  float4 color: COLOR;
209  uint shadow_id: FLAT;
210};
211
212struct Shadow {
213    uint order;
214    float blur_radius;
215    Bounds bounds;
216    Corners corner_radii;
217    Bounds content_mask;
218    Hsla color;
219};
220
221StructuredBuffer<Shadow> shadows: register(t1);
222
223ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV_InstanceID) {
224    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
225    Shadow shadow = shadows[shadow_id];
226
227    float margin = 3.0 * shadow.blur_radius;
228    Bounds bounds = shadow.bounds;
229    bounds.origin -= margin;
230    bounds.size += 2.0 * margin;
231
232    float4 device_position = to_device_position(unit_vertex, bounds);
233    float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask);
234    float4 color = hsla_to_rgba(shadow.color);
235
236    ShadowVertexOutput output;
237    output.position = device_position;
238    output.color = color;
239    output.shadow_id = shadow_id;
240    output.clip_distance = clip_distance;
241
242    return output;
243}
244
245float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
246    Shadow shadow = shadows[input.shadow_id];
247
248    float2 half_size = shadow.bounds.size / 2.;
249    float2 center = shadow.bounds.origin + half_size;
250    float2 point0 = input.position.xy - center;
251    float corner_radius;
252    if (point0.x < 0.) {
253        if (point0.y < 0.) {
254            corner_radius = shadow.corner_radii.top_left;
255        } else {
256            corner_radius = shadow.corner_radii.bottom_left;
257        }
258    } else {
259        if (point0.y < 0.) {
260            corner_radius = shadow.corner_radii.top_right;
261        } else {
262            corner_radius = shadow.corner_radii.bottom_right;
263        }
264    }
265
266    // The signal is only non-zero in a limited range, so don't waste samples
267    float low = point0.y - half_size.y;
268    float high = point0.y + half_size.y;
269    float start = clamp(-3. * shadow.blur_radius, low, high);
270    float end = clamp(3. * shadow.blur_radius, low, high);
271
272    // Accumulate samples (we can get away with surprisingly few samples)
273    float step = (end - start) / 4.;
274    float y = start + step * 0.5;
275    float alpha = 0.;
276    for (int i = 0; i < 4; i++) {
277        alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius,
278                            corner_radius, half_size) *
279                gaussian(y, shadow.blur_radius) * step;
280        y += step;
281    }
282
283    return input.color * float4(1., 1., 1., alpha);
284}
285
286/*
287**
288**              Quads
289**
290*/
291
292struct Quad {
293    uint order;
294    uint pad;
295    Bounds bounds;
296    Bounds content_mask;
297    Hsla background;
298    Hsla border_color;
299    Corners corner_radii;
300    Edges border_widths;
301};
302
303struct QuadVertexOutput {
304    float4 position: SV_Position;
305    float4 background_color: COLOR0;
306    float4 border_color: COLOR1;
307    uint quad_id: FLAT;
308    float4 clip_distance: SV_ClipDistance;
309};
310
311struct QuadFragmentInput {
312    float4 position: SV_Position;
313    float4 background_color: COLOR0;
314    float4 border_color: COLOR1;
315    uint quad_id: FLAT;
316};
317
318StructuredBuffer<Quad> quads: register(t1);
319
320QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_InstanceID) {
321    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
322    Quad quad = quads[quad_id];
323    float4 device_position = to_device_position(unit_vertex, quad.bounds);
324    float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
325    float4 background_color = hsla_to_rgba(quad.background);
326    float4 border_color = hsla_to_rgba(quad.border_color);
327
328    QuadVertexOutput output;
329    output.position = device_position;
330    output.background_color = background_color;
331    output.border_color = border_color;
332    output.quad_id = quad_id;
333    output.clip_distance = clip_distance;
334    return output;
335}
336
337float4 quad_fragment(QuadFragmentInput input): SV_Target {
338    Quad quad = quads[input.quad_id];
339
340    // Fast path when the quad is not rounded and doesn't have any border.
341    if (quad.corner_radii.top_left == 0. && quad.corner_radii.bottom_left == 0. &&
342        quad.corner_radii.top_right == 0. &&
343        quad.corner_radii.bottom_right == 0. && quad.border_widths.top == 0. &&
344        quad.border_widths.left == 0. && quad.border_widths.right == 0. &&
345        quad.border_widths.bottom == 0.) {
346        return input.background_color;
347    }
348
349    float2 half_size = quad.bounds.size / 2.;
350    float2 center = quad.bounds.origin + half_size;
351    float2 center_to_point = input.position.xy - center;
352    float corner_radius;
353    if (center_to_point.x < 0.) {
354        if (center_to_point.y < 0.) {
355            corner_radius = quad.corner_radii.top_left;
356        } else {
357            corner_radius = quad.corner_radii.bottom_left;
358        }
359    } else {
360        if (center_to_point.y < 0.) {
361            corner_radius = quad.corner_radii.top_right;
362        } else {
363            corner_radius = quad.corner_radii.bottom_right;
364        }
365    }
366
367    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
368    float distance =
369        length(max(0., rounded_edge_to_point)) +
370        min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
371        corner_radius;
372
373    float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
374                                                    : quad.border_widths.right;
375    float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
376                                                        : quad.border_widths.bottom;
377    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
378    float2 point_to_inset_corner = abs(center_to_point) - inset_size;
379    float border_width;
380    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
381        border_width = 0.;
382    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
383        border_width = horizontal_border;
384    } else {
385        border_width = vertical_border;
386    }
387
388    float4 color;
389    if (border_width == 0.) {
390        color = input.background_color;
391    } else {
392        float inset_distance = distance + border_width;
393        // Blend the border on top of the background and then linearly interpolate
394        // between the two as we slide inside the background.
395        float4 blended_border = over(input.background_color, input.border_color);
396        color = lerp(blended_border, input.background_color,
397                    saturate(0.5 - inset_distance));
398    }
399
400    return color * float4(1., 1., 1., saturate(0.5 - distance));
401}
402
403/*
404**
405**              Path raster
406**
407*/
408
409struct PathVertex {
410    float2 xy_position;
411    float2 st_position;
412    Bounds content_mask;
413};
414
415struct PathRasterizationOutput {
416    float4 position: SV_Position;
417    float2 st_position: TEXCOORD0;
418    float4 clip_distances: SV_ClipDistance;
419};
420
421struct PathRasterizationInput {
422    float4 position: SV_Position;
423    float2 st_position: TEXCOORD0;
424};
425
426StructuredBuffer<PathVertex> path_vertices: register(t1);
427
428PathRasterizationOutput path_rasterization_vertex(uint vertex_id: SV_VertexID) {
429    PathVertex vertex = path_vertices[vertex_id];
430    PathRasterizationOutput output;
431    float2 device_position = vertex.xy_position / global_viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0);
432    float2 tl = vertex.xy_position - vertex.content_mask.origin;
433    float2 br = vertex.content_mask.origin + vertex.content_mask.size - vertex.xy_position;
434
435    output.position = float4(device_position, 0.0, 1.0);
436    output.st_position = vertex.st_position;
437    output.clip_distances = float4(tl.x, br.x, tl.y, br.y);
438    return output;
439}
440
441float4 path_rasterization_fragment(PathRasterizationInput input): SV_Target {
442    float2 dx = ddx(input.st_position);
443    float2 dy = ddy(input.st_position);
444    float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
445                            (2. * input.st_position.x) * dy.x - dy.y);
446    float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
447    float distance = f / length(gradient);
448    float alpha = saturate(0.5 - distance);
449    return float4(alpha, 0., 0., 1.);
450}
451
452/*
453**
454**              Paths
455**
456*/
457
458struct PathSprite {
459    Bounds bounds;
460    Hsla color;
461    AtlasTile tile;
462};
463
464struct PathVertexOutput {
465    float4 position: SV_Position;
466    float2 tile_position: POSITION1;
467    float4 color: COLOR;
468};
469
470StructuredBuffer<PathSprite> path_sprites: register(t1);
471
472PathVertexOutput paths_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
473    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
474    PathSprite sprite = path_sprites[instance_id];
475    // Don't apply content mask because it was already accounted for when rasterizing the path.
476
477    PathVertexOutput output;
478    output.position = to_device_position(unit_vertex, sprite.bounds);
479    output.tile_position = to_tile_position(unit_vertex, sprite.tile);
480    output.color = hsla_to_rgba(sprite.color);
481    return output;
482}
483
484float4 paths_fragment(PathVertexOutput input): SV_Target {
485    float sample = t_sprite.Sample(s_sprite, input.tile_position).r;
486    float mask = 1.0 - abs(1.0 - sample % 2.0);
487    float4 color = input.color;
488    color.a *= mask;
489    return color;
490}
491
492/*
493**
494**              Underlines
495**
496*/
497
498struct Underline {
499    uint order;
500    uint pad;
501    Bounds bounds;
502    Bounds content_mask;
503    Hsla color;
504    float thickness;
505    uint wavy;
506};
507
508struct UnderlineVertexOutput {
509  float4 position: SV_Position;
510  float4 color: COLOR;
511  uint underline_id: FLAT;
512  float4 clip_distance: SV_ClipDistance;
513};
514
515struct UnderlineFragmentInput {
516  float4 position: SV_Position;
517  float4 color: COLOR;
518  uint underline_id: FLAT;
519};
520
521StructuredBuffer<Underline> underlines: register(t1);
522
523UnderlineVertexOutput underline_vertex(uint vertex_id: SV_VertexID, uint underline_id: SV_InstanceID) {
524    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
525    Underline underline = underlines[underline_id];
526    float4 device_position = to_device_position(unit_vertex, underline.bounds);
527    float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds, 
528                                                    underline.content_mask);
529    float4 color = hsla_to_rgba(underline.color);
530
531    UnderlineVertexOutput output;
532    output.position = device_position;
533    output.color = color;
534    output.underline_id = underline_id;
535    output.clip_distance = clip_distance;
536    return output;
537}
538
539float4 underline_fragment(UnderlineFragmentInput input): SV_Target {
540    Underline underline = underlines[input.underline_id];
541    if (underline.wavy) {
542        float half_thickness = underline.thickness * 0.5;
543        float2 origin =
544            float2(underline.bounds.origin.x, underline.bounds.origin.y);
545        float2 st = ((input.position.xy - origin) / underline.bounds.size.y) -
546                    float2(0., 0.5);
547        float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
548        float amplitude = 1. / (2. * underline.thickness);
549        float sine = sin(st.x * frequency) * amplitude;
550        float dSine = cos(st.x * frequency) * amplitude * frequency;
551        float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
552        float distance_in_pixels = distance * underline.bounds.size.y;
553        float distance_from_top_border = distance_in_pixels - half_thickness;
554        float distance_from_bottom_border = distance_in_pixels + half_thickness;
555        float alpha = saturate(
556            0.5 - max(-distance_from_bottom_border, distance_from_top_border));
557        return input.color * float4(1., 1., 1., alpha);
558    } else {
559        return input.color;
560    }
561}
562
563/*
564**
565**              Monochrome sprites
566**
567*/
568
569struct MonochromeSprite {
570    uint order;
571    uint pad;
572    Bounds bounds;
573    Bounds content_mask;
574    Hsla color;
575    AtlasTile tile;
576    TransformationMatrix transformation;
577};
578
579struct MonochromeSpriteVertexOutput {
580    float4 position: SV_Position;
581    float2 tile_position: POSITION;
582    float4 color: COLOR;
583    float4 clip_distance: SV_ClipDistance;
584};
585
586struct MonochromeSpriteFragmentInput {
587    float4 position: SV_Position;
588    float2 tile_position: POSITION;
589    float4 color: COLOR;
590};
591
592StructuredBuffer<MonochromeSprite> mono_sprites: register(t1);
593
594MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
595    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
596    MonochromeSprite sprite = mono_sprites[sprite_id];
597    float4 device_position =
598        to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
599    float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
600    float2 tile_position = to_tile_position(unit_vertex, sprite.tile);
601    float4 color = hsla_to_rgba(sprite.color);
602
603    MonochromeSpriteVertexOutput output;
604    output.position = device_position;
605    output.tile_position = tile_position;
606    output.color = color;
607    output.clip_distance = clip_distance;
608    return output;
609}
610
611float4 monochrome_sprite_fragment(MonochromeSpriteFragmentInput input): SV_Target {
612    float4 sample = t_sprite.Sample(s_sprite, input.tile_position);
613    float4 color = input.color;
614    color.a *= sample.a;
615    return color;
616}
617
618/*
619**
620**              Polychrome sprites
621**
622*/
623
624struct PolychromeSprite {
625    uint order;
626    uint grayscale;
627    Bounds bounds;
628    Bounds content_mask;
629    Corners corner_radii;
630    AtlasTile tile;
631};
632
633struct PolychromeSpriteVertexOutput {
634    float4 position: SV_Position;
635    float2 tile_position: POSITION;
636    uint sprite_id: FLAT;
637    float4 clip_distance: SV_ClipDistance;
638};
639
640struct PolychromeSpriteFragmentInput {
641    float4 position: SV_Position;
642    float2 tile_position: POSITION;
643    uint sprite_id: FLAT;
644};
645
646StructuredBuffer<PolychromeSprite> poly_sprites: register(t1);
647
648PolychromeSpriteVertexOutput polychrome_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
649    float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
650    PolychromeSprite sprite = poly_sprites[sprite_id];
651    float4 device_position = to_device_position(unit_vertex, sprite.bounds);
652    float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
653                                                    sprite.content_mask);
654    float2 tile_position = to_tile_position(unit_vertex, sprite.tile);
655
656    PolychromeSpriteVertexOutput output;
657    output.position = device_position;
658    output.tile_position = tile_position;
659    output.sprite_id = sprite_id;
660    output.clip_distance = clip_distance;
661    return output;
662}
663
664float4 polychrome_sprite_fragment(PolychromeSpriteFragmentInput input): SV_Target {
665    PolychromeSprite sprite = poly_sprites[input.sprite_id];
666    float4 sample = t_sprite.Sample(s_sprite, input.tile_position);
667    float distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
668
669    float4 color = sample;
670    if ((sprite.grayscale & 0xFFu) != 0u) {
671        float3 grayscale = dot(color.rgb, GRAYSCALE_FACTORS);
672        color = float4(grayscale, sample.a);
673    }
674    color.a *= saturate(0.5 - distance);
675    return color;
676}