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}