1// Shader model 4.0 port of draw.wgsl, for Direct3D 11 from feature level 10_0. The render
2// module embeds its bytecode, compiled by render/dxbc/compile.ps1.
3struct Vertex {
4 float4 position : SV_Position;
5 float2 uv : TEXCOORD0;
6 float4 color : TEXCOORD1;
7 float2 local : TEXCOORD2;
8 nointerpolation float4 shape : TEXCOORD3;
9 nointerpolation float stroke : TEXCOORD4;
10 float2 clip_local : TEXCOORD5;
11 nointerpolation float3 clip : TEXCOORD6;
12 nointerpolation float blur : TEXCOORD7;
13};
14
15Texture2D atlas : register(t0);
16SamplerState atlas_sampler : register(s0);
17
18Vertex vertex(float2 position : TEXCOORD0, float2 uv : TEXCOORD1, float4 color : TEXCOORD2,
19 float2 local : TEXCOORD3, float4 shape : TEXCOORD4, float stroke : TEXCOORD5,
20 float2 clip_local : TEXCOORD6, float3 clip : TEXCOORD7, float blur : TEXCOORD8) {
21 Vertex output;
22 output.position = float4(position, 0.0, 1.0);
23 output.uv = uv;
24 output.color = color;
25 output.local = local;
26 output.shape = shape;
27 output.stroke = stroke;
28 output.clip_local = clip_local;
29 output.clip = clip;
30 output.blur = blur;
31 return output;
32}
33
34float ellipse_arc(float angle, float2 radius) {
35 if (radius.x == radius.y) {
36 return angle * radius.x;
37 }
38 // Four-point Gauss-Legendre quadrature.
39 const float4 nodes = float4(-0.86113631, -0.33998104, 0.33998104, 0.86113631);
40 const float4 weights = float4(0.34785485, 0.65214515, 0.65214515, 0.34785485);
41 float half_angle = angle * 0.5;
42 float scale = max(radius.x, radius.y);
43 float sum = 0.0;
44 [unroll] for (int i = 0; i < 4; i++) {
45 float t = half_angle * (nodes[i] + 1.0);
46 sum += weights[i] * length((radius / scale) * float2(sin(t), cos(t)));
47 }
48 return half_angle * sum * scale;
49}
50
51float2 erf(float2 x) {
52 float2 s = sign(x);
53 float2 a = abs(x);
54 float2 y = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
55 y *= y;
56 return s - s / (y * y);
57}
58
59float shadow(float2 p, float2 half_size, float corner, float sigma) {
60 float start = clamp(-3.0 * sigma, p.y - half_size.y, p.y + half_size.y);
61 float end = clamp(3.0 * sigma, p.y - half_size.y, p.y + half_size.y);
62 float stride = (end - start) / 4.0;
63 float y = start + stride * 0.5;
64 float value = 0.0;
65 [unroll] for (int i = 0; i < 4; i++) {
66 float delta = min(half_size.y - corner - abs(p.y - y), 0.0);
67 float curved = half_size.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
68 float2 integral = 0.5 + 0.5 * erf((p.x + float2(-curved, curved)) * (0.70710678 / sigma));
69 float weight = exp(-y * y / (2.0 * sigma * sigma)) / (2.50662827 * sigma);
70 value += (integral.y - integral.x) * weight * stride;
71 y += stride;
72 }
73 return value;
74}
75
76float taper(float2 p, float h, float r0, float r1) {
77 float2 q = float2(abs(p.y), p.x + h);
78 float span = 2.0 * h;
79 float b = (r0 - r1) / max(span, 1e-6);
80 float far = length(q - float2(0.0, span)) - r1;
81 if (abs(b) >= 1.0) {
82 return min(length(q) - r0, far);
83 }
84 float a = sqrt(1.0 - b * b);
85 float k = dot(q, float2(-b, a));
86 if (k < 0.0) {
87 return length(q) - r0;
88 }
89 if (k > a * span) {
90 return far;
91 }
92 return dot(q, float2(a, b)) - r0;
93}
94
95float4 fragment(Vertex input) : SV_Target {
96 float4 color = atlas.Sample(atlas_sampler, input.uv) * input.color;
97 if (input.blur > 0.0) {
98 color *= shadow(input.local, input.shape.xy, input.shape.z, input.blur);
99 } else if (input.blur < 0.0) {
100 color *= saturate(0.5 - taper(input.local, input.shape.x, input.shape.y, input.shape.z));
101 } else if (input.shape.x > 0.0 && input.shape.y > 0.0) {
102 float2 q = abs(input.local) - input.shape.xy + input.shape.zw;
103 float distance;
104 if (input.shape.z == input.shape.w) {
105 distance = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - input.shape.z;
106 } else if (all(q > 0.0)) {
107 float2 radius = max(input.shape.zw, 0.0001);
108 float2 normalized = q / radius;
109 float k0 = length(normalized);
110 distance = k0 * (k0 - 1.0) / max(length(normalized / radius), 1e-20);
111 } else {
112 float2 edge = abs(input.local) - input.shape.xy;
113 distance = max(edge.x, edge.y);
114 }
115 float coverage = saturate(0.5 - distance);
116 float width = abs(input.stroke);
117 if (width > 0.0) {
118 coverage *= saturate(distance + width + 0.5);
119 }
120 if (input.stroke < 0.0) {
121 float2 radius = max(input.shape.zw - width * 0.5, 0.0);
122 if (any(radius == 0.0)) {
123 radius = 0.0;
124 }
125 float2 straight = input.shape.xy - width * 0.5 - radius;
126 float2 p = abs(input.local);
127 float quarter = straight.x + straight.y + ellipse_arc(1.57079632679, radius);
128 float along;
129 if (p.y <= straight.y) {
130 along = p.y;
131 } else if (p.x <= straight.x || radius.x == 0.0) {
132 along = quarter - p.x;
133 } else {
134 float angle = atan2((p.y - straight.y) / radius.y, (p.x - straight.x) / radius.x);
135 along = straight.y + ellipse_arc(angle, radius);
136 }
137 if (input.local.x < 0.0) {
138 along = input.local.y < 0.0 ? 2.0 * quarter + along : 2.0 * quarter - along;
139 } else if (input.local.y < 0.0) {
140 along = 4.0 * quarter - along;
141 }
142 float phase = frac(along / (4.0 * width)) * (4.0 * width);
143 coverage *= saturate(width + 0.5 - abs(phase - 2.0 * width));
144 }
145 color *= coverage;
146 }
147 if (input.clip.x > 0.0) {
148 float2 q = abs(input.clip_local) - input.clip.xy + input.clip.z;
149 float distance = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - input.clip.z;
150 color *= saturate(0.5 - distance);
151 }
152 return color;
153}