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}