1#include <metal_stdlib>
2#include <simd/simd.h>
3
4using namespace metal;
5
6float4 hsla_to_rgba(Hsla hsla);
7float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
8 constant Size_DevicePixels *viewport_size);
9float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
10 constant Size_DevicePixels *atlas_size);
11float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
12 Bounds_ScaledPixels clip_bounds);
13float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
14 Corners_ScaledPixels corner_radii);
15float gaussian(float x, float sigma);
16float2 erf(float2 x);
17float blur_along_x(float x, float y, float sigma, float corner,
18 float2 half_size);
19
20struct QuadVertexOutput {
21 float4 position [[position]];
22 float4 background_color [[flat]];
23 float4 border_color [[flat]];
24 uint quad_id [[flat]];
25 float clip_distance [[clip_distance]][4];
26};
27
28struct QuadFragmentInput {
29 float4 position [[position]];
30 float4 background_color [[flat]];
31 float4 border_color [[flat]];
32 uint quad_id [[flat]];
33};
34
35vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
36 uint quad_id [[instance_id]],
37 constant float2 *unit_vertices
38 [[buffer(QuadInputIndex_Vertices)]],
39 constant Quad *quads
40 [[buffer(QuadInputIndex_Quads)]],
41 constant Size_DevicePixels *viewport_size
42 [[buffer(QuadInputIndex_ViewportSize)]]) {
43 float2 unit_vertex = unit_vertices[unit_vertex_id];
44 Quad quad = quads[quad_id];
45 float4 device_position =
46 to_device_position(unit_vertex, quad.bounds, viewport_size);
47 float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
48 quad.content_mask.bounds);
49 float4 background_color = hsla_to_rgba(quad.background);
50 float4 border_color = hsla_to_rgba(quad.border_color);
51 return QuadVertexOutput{
52 device_position,
53 background_color,
54 border_color,
55 quad_id,
56 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
57}
58
59fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
60 constant Quad *quads
61 [[buffer(QuadInputIndex_Quads)]]) {
62 Quad quad = quads[input.quad_id];
63 float2 half_size =
64 float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
65 float2 center =
66 float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
67 float2 center_to_point = input.position.xy - center;
68 float corner_radius;
69 if (center_to_point.x < 0.) {
70 if (center_to_point.y < 0.) {
71 corner_radius = quad.corner_radii.top_left;
72 } else {
73 corner_radius = quad.corner_radii.bottom_left;
74 }
75 } else {
76 if (center_to_point.y < 0.) {
77 corner_radius = quad.corner_radii.top_right;
78 } else {
79 corner_radius = quad.corner_radii.bottom_right;
80 }
81 }
82
83 float2 rounded_edge_to_point =
84 fabs(center_to_point) - half_size + corner_radius;
85 float distance =
86 length(max(0., rounded_edge_to_point)) +
87 min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
88 corner_radius;
89
90 float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
91 : quad.border_widths.right;
92 float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
93 : quad.border_widths.bottom;
94 float2 inset_size =
95 half_size - corner_radius - float2(vertical_border, horizontal_border);
96 float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
97 float border_width;
98 if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
99 border_width = 0.;
100 } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
101 border_width = horizontal_border;
102 } else {
103 border_width = vertical_border;
104 }
105
106 float4 color;
107 if (border_width == 0.) {
108 color = input.background_color;
109 } else {
110 float inset_distance = distance + border_width;
111
112 // Decrease border's opacity as we move inside the background.
113 input.border_color.a *= 1. - saturate(0.5 - inset_distance);
114
115 // Alpha-blend the border and the background.
116 float output_alpha = input.border_color.a +
117 input.background_color.a * (1. - input.border_color.a);
118 float3 premultiplied_border_rgb =
119 input.border_color.rgb * input.border_color.a;
120 float3 premultiplied_background_rgb =
121 input.background_color.rgb * input.background_color.a;
122 float3 premultiplied_output_rgb =
123 premultiplied_border_rgb +
124 premultiplied_background_rgb * (1. - input.border_color.a);
125 color = float4(premultiplied_output_rgb, output_alpha);
126 }
127
128 return color * float4(1., 1., 1., saturate(0.5 - distance));
129}
130
131struct ShadowVertexOutput {
132 float4 position [[position]];
133 float4 color [[flat]];
134 uint shadow_id [[flat]];
135 float clip_distance [[clip_distance]][4];
136};
137
138struct ShadowFragmentInput {
139 float4 position [[position]];
140 float4 color [[flat]];
141 uint shadow_id [[flat]];
142};
143
144vertex ShadowVertexOutput shadow_vertex(
145 uint unit_vertex_id [[vertex_id]], uint shadow_id [[instance_id]],
146 constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
147 constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
148 constant Size_DevicePixels *viewport_size
149 [[buffer(ShadowInputIndex_ViewportSize)]]) {
150 float2 unit_vertex = unit_vertices[unit_vertex_id];
151 Shadow shadow = shadows[shadow_id];
152
153 float margin = 3. * shadow.blur_radius;
154 // Set the bounds of the shadow and adjust its size based on the shadow's
155 // spread radius to achieve the spreading effect
156 Bounds_ScaledPixels bounds = shadow.bounds;
157 bounds.origin.x -= margin;
158 bounds.origin.y -= margin;
159 bounds.size.width += 2. * margin;
160 bounds.size.height += 2. * margin;
161
162 float4 device_position =
163 to_device_position(unit_vertex, bounds, viewport_size);
164 float4 clip_distance =
165 distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
166 float4 color = hsla_to_rgba(shadow.color);
167
168 return ShadowVertexOutput{
169 device_position,
170 color,
171 shadow_id,
172 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
173}
174
175fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
176 constant Shadow *shadows
177 [[buffer(ShadowInputIndex_Shadows)]]) {
178 Shadow shadow = shadows[input.shadow_id];
179
180 float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
181 float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
182 float2 half_size = size / 2.;
183 float2 center = origin + half_size;
184 float2 point = input.position.xy - center;
185 float corner_radius;
186 if (point.x < 0.) {
187 if (point.y < 0.) {
188 corner_radius = shadow.corner_radii.top_left;
189 } else {
190 corner_radius = shadow.corner_radii.bottom_left;
191 }
192 } else {
193 if (point.y < 0.) {
194 corner_radius = shadow.corner_radii.top_right;
195 } else {
196 corner_radius = shadow.corner_radii.bottom_right;
197 }
198 }
199
200 // The signal is only non-zero in a limited range, so don't waste samples
201 float low = point.y - half_size.y;
202 float high = point.y + half_size.y;
203 float start = clamp(-3. * shadow.blur_radius, low, high);
204 float end = clamp(3. * shadow.blur_radius, low, high);
205
206 // Accumulate samples (we can get away with surprisingly few samples)
207 float step = (end - start) / 4.;
208 float y = start + step * 0.5;
209 float alpha = 0.;
210 for (int i = 0; i < 4; i++) {
211 alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
212 corner_radius, half_size) *
213 gaussian(y, shadow.blur_radius) * step;
214 y += step;
215 }
216
217 return input.color * float4(1., 1., 1., alpha);
218}
219
220struct UnderlineVertexOutput {
221 float4 position [[position]];
222 float4 color [[flat]];
223 uint underline_id [[flat]];
224 float clip_distance [[clip_distance]][4];
225};
226
227struct UnderlineFragmentInput {
228 float4 position [[position]];
229 float4 color [[flat]];
230 uint underline_id [[flat]];
231};
232
233vertex UnderlineVertexOutput underline_vertex(
234 uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
235 constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
236 constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
237 constant Size_DevicePixels *viewport_size
238 [[buffer(ShadowInputIndex_ViewportSize)]]) {
239 float2 unit_vertex = unit_vertices[unit_vertex_id];
240 Underline underline = underlines[underline_id];
241 float4 device_position =
242 to_device_position(unit_vertex, underline.bounds, viewport_size);
243 float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
244 underline.content_mask.bounds);
245 float4 color = hsla_to_rgba(underline.color);
246 return UnderlineVertexOutput{
247 device_position,
248 color,
249 underline_id,
250 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
251}
252
253fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
254 constant Underline *underlines
255 [[buffer(UnderlineInputIndex_Underlines)]]) {
256 Underline underline = underlines[input.underline_id];
257 if (underline.wavy) {
258 float half_thickness = underline.thickness * 0.5;
259 float2 origin =
260 float2(underline.bounds.origin.x, underline.bounds.origin.y);
261 float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
262 float2(0., 0.5);
263 float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
264 float amplitude = 1. / (2. * underline.thickness);
265 float sine = sin(st.x * frequency) * amplitude;
266 float dSine = cos(st.x * frequency) * amplitude * frequency;
267 float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
268 float distance_in_pixels = distance * underline.bounds.size.height;
269 float distance_from_top_border = distance_in_pixels - half_thickness;
270 float distance_from_bottom_border = distance_in_pixels + half_thickness;
271 float alpha = saturate(
272 0.5 - max(-distance_from_bottom_border, distance_from_top_border));
273 return input.color * float4(1., 1., 1., alpha);
274 } else {
275 return input.color;
276 }
277}
278
279struct MonochromeSpriteVertexOutput {
280 float4 position [[position]];
281 float2 tile_position;
282 float4 color [[flat]];
283 float clip_distance [[clip_distance]][4];
284};
285
286struct MonochromeSpriteFragmentInput {
287 float4 position [[position]];
288 float2 tile_position;
289 float4 color [[flat]];
290};
291
292vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
293 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
294 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
295 constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
296 constant Size_DevicePixels *viewport_size
297 [[buffer(SpriteInputIndex_ViewportSize)]],
298 constant Size_DevicePixels *atlas_size
299 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
300 float2 unit_vertex = unit_vertices[unit_vertex_id];
301 MonochromeSprite sprite = sprites[sprite_id];
302 float4 device_position =
303 to_device_position(unit_vertex, sprite.bounds, viewport_size);
304 float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
305 sprite.content_mask.bounds);
306 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
307 float4 color = hsla_to_rgba(sprite.color);
308 return MonochromeSpriteVertexOutput{
309 device_position,
310 tile_position,
311 color,
312 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
313}
314
315fragment float4 monochrome_sprite_fragment(
316 MonochromeSpriteFragmentInput input [[stage_in]],
317 constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
318 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
319 constexpr sampler atlas_texture_sampler(mag_filter::linear,
320 min_filter::linear);
321 float4 sample =
322 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
323 float4 color = input.color;
324 color.a *= sample.a;
325 return color;
326}
327
328struct PolychromeSpriteVertexOutput {
329 float4 position [[position]];
330 float2 tile_position;
331 uint sprite_id [[flat]];
332 float clip_distance [[clip_distance]][4];
333};
334
335struct PolychromeSpriteFragmentInput {
336 float4 position [[position]];
337 float2 tile_position;
338 uint sprite_id [[flat]];
339};
340
341vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
342 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
343 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
344 constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
345 constant Size_DevicePixels *viewport_size
346 [[buffer(SpriteInputIndex_ViewportSize)]],
347 constant Size_DevicePixels *atlas_size
348 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
349
350 float2 unit_vertex = unit_vertices[unit_vertex_id];
351 PolychromeSprite sprite = sprites[sprite_id];
352 float4 device_position =
353 to_device_position(unit_vertex, sprite.bounds, viewport_size);
354 float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
355 sprite.content_mask.bounds);
356 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
357 return PolychromeSpriteVertexOutput{
358 device_position,
359 tile_position,
360 sprite_id,
361 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
362}
363
364fragment float4 polychrome_sprite_fragment(
365 PolychromeSpriteFragmentInput input [[stage_in]],
366 constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
367 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
368 PolychromeSprite sprite = sprites[input.sprite_id];
369 constexpr sampler atlas_texture_sampler(mag_filter::linear,
370 min_filter::linear);
371 float4 sample =
372 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
373 float distance =
374 quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
375
376 float4 color = sample;
377 if (sprite.grayscale) {
378 float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
379 color.r = grayscale;
380 color.g = grayscale;
381 color.b = grayscale;
382 }
383 color.a *= saturate(0.5 - distance);
384 return color;
385}
386
387struct PathRasterizationVertexOutput {
388 float4 position [[position]];
389 float2 st_position;
390 float clip_rect_distance [[clip_distance]][4];
391};
392
393struct PathRasterizationFragmentInput {
394 float4 position [[position]];
395 float2 st_position;
396};
397
398vertex PathRasterizationVertexOutput path_rasterization_vertex(
399 uint vertex_id [[vertex_id]],
400 constant PathVertex_ScaledPixels *vertices
401 [[buffer(PathRasterizationInputIndex_Vertices)]],
402 constant Size_DevicePixels *atlas_size
403 [[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
404 PathVertex_ScaledPixels v = vertices[vertex_id];
405 float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
406 float2 viewport_size = float2(atlas_size->width, atlas_size->height);
407 return PathRasterizationVertexOutput{
408 float4(vertex_position / viewport_size * float2(2., -2.) +
409 float2(-1., 1.),
410 0., 1.),
411 float2(v.st_position.x, v.st_position.y),
412 {v.xy_position.x - v.content_mask.bounds.origin.x,
413 v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
414 v.xy_position.x,
415 v.xy_position.y - v.content_mask.bounds.origin.y,
416 v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
417 v.xy_position.y}};
418}
419
420fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
421 [[stage_in]]) {
422 float2 dx = dfdx(input.st_position);
423 float2 dy = dfdy(input.st_position);
424 float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
425 (2. * input.st_position.x) * dy.x - dy.y);
426 float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
427 float distance = f / length(gradient);
428 float alpha = saturate(0.5 - distance);
429 return float4(alpha, 0., 0., 1.);
430}
431
432struct PathSpriteVertexOutput {
433 float4 position [[position]];
434 float2 tile_position;
435 float4 color [[flat]];
436};
437
438vertex PathSpriteVertexOutput path_sprite_vertex(
439 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
440 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
441 constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
442 constant Size_DevicePixels *viewport_size
443 [[buffer(SpriteInputIndex_ViewportSize)]],
444 constant Size_DevicePixels *atlas_size
445 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
446
447 float2 unit_vertex = unit_vertices[unit_vertex_id];
448 PathSprite sprite = sprites[sprite_id];
449 // Don't apply content mask because it was already accounted for when
450 // rasterizing the path.
451 float4 device_position =
452 to_device_position(unit_vertex, sprite.bounds, viewport_size);
453 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
454 float4 color = hsla_to_rgba(sprite.color);
455 return PathSpriteVertexOutput{device_position, tile_position, color};
456}
457
458fragment float4 path_sprite_fragment(
459 PathSpriteVertexOutput input [[stage_in]],
460 constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
461 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
462 constexpr sampler atlas_texture_sampler(mag_filter::linear,
463 min_filter::linear);
464 float4 sample =
465 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
466 float mask = 1. - abs(1. - fmod(sample.r, 2.));
467 float4 color = input.color;
468 color.a *= mask;
469 return color;
470}
471
472float4 hsla_to_rgba(Hsla hsla) {
473 float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
474 float s = hsla.s;
475 float l = hsla.l;
476 float a = hsla.a;
477
478 float c = (1.0 - fabs(2.0 * l - 1.0)) * s;
479 float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
480 float m = l - c / 2.0;
481
482 float r = 0.0;
483 float g = 0.0;
484 float b = 0.0;
485
486 if (h >= 0.0 && h < 1.0) {
487 r = c;
488 g = x;
489 b = 0.0;
490 } else if (h >= 1.0 && h < 2.0) {
491 r = x;
492 g = c;
493 b = 0.0;
494 } else if (h >= 2.0 && h < 3.0) {
495 r = 0.0;
496 g = c;
497 b = x;
498 } else if (h >= 3.0 && h < 4.0) {
499 r = 0.0;
500 g = x;
501 b = c;
502 } else if (h >= 4.0 && h < 5.0) {
503 r = x;
504 g = 0.0;
505 b = c;
506 } else {
507 r = c;
508 g = 0.0;
509 b = x;
510 }
511
512 float4 rgba;
513 rgba.x = (r + m);
514 rgba.y = (g + m);
515 rgba.z = (b + m);
516 rgba.w = a;
517 return rgba;
518}
519
520float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
521 constant Size_DevicePixels *input_viewport_size) {
522 float2 position =
523 unit_vertex * float2(bounds.size.width, bounds.size.height) +
524 float2(bounds.origin.x, bounds.origin.y);
525 float2 viewport_size = float2((float)input_viewport_size->width,
526 (float)input_viewport_size->height);
527 float2 device_position =
528 position / viewport_size * float2(2., -2.) + float2(-1., 1.);
529 return float4(device_position, 0., 1.);
530}
531
532float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
533 constant Size_DevicePixels *atlas_size) {
534 float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
535 float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
536 return (tile_origin + unit_vertex * tile_size) /
537 float2((float)atlas_size->width, (float)atlas_size->height);
538}
539
540float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
541 Corners_ScaledPixels corner_radii) {
542 float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
543 float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
544 float2 center_to_point = point - center;
545 float corner_radius;
546 if (center_to_point.x < 0.) {
547 if (center_to_point.y < 0.) {
548 corner_radius = corner_radii.top_left;
549 } else {
550 corner_radius = corner_radii.bottom_left;
551 }
552 } else {
553 if (center_to_point.y < 0.) {
554 corner_radius = corner_radii.top_right;
555 } else {
556 corner_radius = corner_radii.bottom_right;
557 }
558 }
559
560 float2 rounded_edge_to_point =
561 abs(center_to_point) - half_size + corner_radius;
562 float distance =
563 length(max(0., rounded_edge_to_point)) +
564 min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
565 corner_radius;
566
567 return distance;
568}
569
570// A standard gaussian function, used for weighting samples
571float gaussian(float x, float sigma) {
572 return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
573}
574
575// This approximates the error function, needed for the gaussian integral
576float2 erf(float2 x) {
577 float2 s = sign(x);
578 float2 a = abs(x);
579 x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
580 x *= x;
581 return s - s / (x * x);
582}
583
584float blur_along_x(float x, float y, float sigma, float corner,
585 float2 half_size) {
586 float delta = min(half_size.y - corner - abs(y), 0.);
587 float curved =
588 half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
589 float2 integral =
590 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
591 return integral.y - integral.x;
592}
593
594float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
595 Bounds_ScaledPixels clip_bounds) {
596 float2 position =
597 unit_vertex * float2(bounds.size.width, bounds.size.height) +
598 float2(bounds.origin.x, bounds.origin.y);
599 return float4(position.x - clip_bounds.origin.x,
600 clip_bounds.origin.x + clip_bounds.size.width - position.x,
601 position.y - clip_bounds.origin.y,
602 clip_bounds.origin.y + clip_bounds.size.height - position.y);
603}