211 lines
6.9 KiB
GLSL
211 lines
6.9 KiB
GLSL
#version 450 core
|
|
|
|
// --- Inputs from vertex shader ---
|
|
layout(location = 0) in vec4 f_color;
|
|
layout(location = 1) in vec2 f_local_or_uv;
|
|
layout(location = 2) in vec4 f_params;
|
|
layout(location = 3) in vec4 f_params2;
|
|
layout(location = 4) flat in uint f_kind_flags;
|
|
layout(location = 5) flat in float f_rotation;
|
|
|
|
// --- Output ---
|
|
layout(location = 0) out vec4 out_color;
|
|
|
|
// --- Texture sampler (for tessellated/text path) ---
|
|
layout(set = 2, binding = 0) uniform sampler2D tex;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SDF helper functions (Inigo Quilez)
|
|
// All operate in physical pixel space — no dpi_scale needed here.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const float PI = 3.14159265358979;
|
|
|
|
float sdCircle(vec2 p, float r) {
|
|
return length(p) - r;
|
|
}
|
|
|
|
float sdRoundedBox(vec2 p, vec2 b, vec4 r) {
|
|
r.xy = (p.x > 0.0) ? r.xy : r.zw;
|
|
r.x = (p.y > 0.0) ? r.x : r.y;
|
|
vec2 q = abs(p) - b + r.x;
|
|
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - r.x;
|
|
}
|
|
|
|
float sdSegment(vec2 p, vec2 a, vec2 b) {
|
|
vec2 pa = p - a, ba = b - a;
|
|
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
|
return length(pa - ba * h);
|
|
}
|
|
|
|
float sdEllipse(vec2 p, vec2 ab) {
|
|
p = abs(p);
|
|
if (p.x > p.y) {
|
|
p = p.yx;
|
|
ab = ab.yx;
|
|
}
|
|
float l = ab.y * ab.y - ab.x * ab.x;
|
|
float m = ab.x * p.x / l;
|
|
float m2 = m * m;
|
|
float n = ab.y * p.y / l;
|
|
float n2 = n * n;
|
|
float c = (m2 + n2 - 1.0) / 3.0;
|
|
float c3 = c * c * c;
|
|
float q = c3 + m2 * n2 * 2.0;
|
|
float d = c3 + m2 * n2;
|
|
float g = m + m * n2;
|
|
float co;
|
|
if (d < 0.0) {
|
|
float h = acos(q / c3) / 3.0;
|
|
float s = cos(h);
|
|
float t = sin(h) * sqrt(3.0);
|
|
float rx = sqrt(-c * (s + t + 2.0) + m2);
|
|
float ry = sqrt(-c * (s - t + 2.0) + m2);
|
|
co = (ry + sign(l) * rx + abs(g) / (rx * ry) - m) / 2.0;
|
|
} else {
|
|
float h = 2.0 * m * n * sqrt(d);
|
|
float s = sign(q + h) * pow(abs(q + h), 1.0 / 3.0);
|
|
float u = sign(q - h) * pow(abs(q - h), 1.0 / 3.0);
|
|
float rx = -s - u - c * 4.0 + 2.0 * m2;
|
|
float ry = (s - u) * sqrt(3.0);
|
|
float rm = sqrt(rx * rx + ry * ry);
|
|
co = (ry / sqrt(rm - rx) + 2.0 * g / rm - m) / 2.0;
|
|
}
|
|
vec2 r = ab * vec2(co, sqrt(1.0 - co * co));
|
|
return length(r - p) * sign(p.y - r.y);
|
|
}
|
|
|
|
float sdf_alpha(float d, float soft) {
|
|
return 1.0 - smoothstep(-soft, soft, d);
|
|
}
|
|
|
|
float sdf_stroke(float d, float stroke_width) {
|
|
return abs(d) - stroke_width * 0.5;
|
|
}
|
|
|
|
// Rotate a 2D point by the negative of the given angle (inverse rotation).
|
|
// Used to rotate the sampling frame opposite to the shape's rotation so that
|
|
// the SDF evaluates correctly for the rotated shape.
|
|
vec2 apply_rotation(vec2 p, float angle) {
|
|
float cr = cos(-angle);
|
|
float sr = sin(-angle);
|
|
return mat2(cr, sr, -sr, cr) * p;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// main
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void main() {
|
|
uint kind = f_kind_flags & 0xFFu;
|
|
uint flags = (f_kind_flags >> 8u) & 0xFFu;
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Kind 0: Tessellated path. Texture multiply for text atlas,
|
|
// white pixel for solid shapes.
|
|
// -----------------------------------------------------------------------
|
|
if (kind == 0u) {
|
|
out_color = f_color * texture(tex, f_local_or_uv);
|
|
return;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// SDF path. f_local_or_uv = shape-centered position in physical pixels.
|
|
// All dimensional params are already in physical pixels (CPU pre-scaled).
|
|
// -----------------------------------------------------------------------
|
|
float d = 1e30;
|
|
float soft = 1.0;
|
|
|
|
if (kind == 1u) {
|
|
// RRect: rounded box
|
|
vec2 b = f_params.xy; // half_size (phys px)
|
|
vec4 r = vec4(f_params.zw, f_params2.xy); // corner radii: tr, br, tl, bl
|
|
soft = max(f_params2.z, 1.0);
|
|
float stroke_px = f_params2.w;
|
|
|
|
vec2 p_local = f_local_or_uv;
|
|
if (f_rotation != 0.0) {
|
|
p_local = apply_rotation(p_local, f_rotation);
|
|
}
|
|
|
|
d = sdRoundedBox(p_local, b, r);
|
|
if ((flags & 1u) != 0u) d = sdf_stroke(d, stroke_px);
|
|
}
|
|
else if (kind == 2u) {
|
|
// Circle — rotationally symmetric, no rotation needed
|
|
float radius = f_params.x;
|
|
soft = max(f_params.y, 1.0);
|
|
float stroke_px = f_params.z;
|
|
|
|
d = sdCircle(f_local_or_uv, radius);
|
|
if ((flags & 1u) != 0u) d = sdf_stroke(d, stroke_px);
|
|
}
|
|
else if (kind == 3u) {
|
|
// Ellipse
|
|
vec2 ab = f_params.xy;
|
|
soft = max(f_params.z, 1.0);
|
|
float stroke_px = f_params.w;
|
|
|
|
vec2 p_local = f_local_or_uv;
|
|
if (f_rotation != 0.0) {
|
|
p_local = apply_rotation(p_local, f_rotation);
|
|
}
|
|
|
|
d = sdEllipse(p_local, ab);
|
|
if ((flags & 1u) != 0u) d = sdf_stroke(d, stroke_px);
|
|
}
|
|
else if (kind == 4u) {
|
|
// Segment (capsule line) — no rotation (excluded)
|
|
vec2 a = f_params.xy; // already in local physical pixels
|
|
vec2 b = f_params.zw;
|
|
float width = f_params2.x;
|
|
soft = max(f_params2.y, 1.0);
|
|
|
|
d = sdSegment(f_local_or_uv, a, b) - width * 0.5;
|
|
}
|
|
else if (kind == 5u) {
|
|
// Ring / Arc — rotation handled by CPU angle offset, no shader rotation
|
|
float inner = f_params.x;
|
|
float outer = f_params.y;
|
|
float start_rad = f_params.z;
|
|
float end_rad = f_params.w;
|
|
soft = max(f_params2.x, 1.0);
|
|
|
|
float r = length(f_local_or_uv);
|
|
float d_ring = max(inner - r, r - outer);
|
|
|
|
// Angular clip
|
|
float angle = atan(f_local_or_uv.y, f_local_or_uv.x);
|
|
if (angle < 0.0) angle += 2.0 * PI;
|
|
float ang_start = mod(start_rad, 2.0 * PI);
|
|
float ang_end = mod(end_rad, 2.0 * PI);
|
|
|
|
float in_arc = (ang_end > ang_start)
|
|
? ((angle >= ang_start && angle <= ang_end) ? 1.0 : 0.0) : ((angle >= ang_start || angle <= ang_end) ? 1.0 : 0.0);
|
|
if (abs(ang_end - ang_start) >= 2.0 * PI - 0.001) in_arc = 1.0;
|
|
|
|
d = in_arc > 0.5 ? d_ring : 1e30;
|
|
}
|
|
else if (kind == 6u) {
|
|
// Regular N-gon — has its own rotation in params, no Primitive.rotation used
|
|
float radius = f_params.x;
|
|
float rotation = f_params.y;
|
|
float sides = f_params.z;
|
|
soft = max(f_params.w, 1.0);
|
|
float stroke_px = f_params2.x;
|
|
|
|
vec2 p = f_local_or_uv;
|
|
float c = cos(rotation), s = sin(rotation);
|
|
p = mat2(c, -s, s, c) * p;
|
|
|
|
float an = PI / sides;
|
|
float bn = mod(atan(p.y, p.x), 2.0 * an) - an;
|
|
d = length(p) * cos(bn) - radius;
|
|
|
|
if ((flags & 1u) != 0u) d = sdf_stroke(d, stroke_px);
|
|
}
|
|
|
|
float alpha = sdf_alpha(d, soft);
|
|
out_color = vec4(f_color.rgb, f_color.a * alpha);
|
|
}
|