draw-improvements (#17)

Major rework to draw rendering system. We are making a SDF first rendering system with tesselated stuff only as a fallback strategy for specific situations where SDF is particularly poorly suited

Co-authored-by: Zachary Levy <zachary@sunforge.is>
Reviewed-on: #17
This commit was merged in pull request #17.
This commit is contained in:
2026-04-24 07:57:44 +00:00
parent 37da2ea068
commit bca19277b3
15 changed files with 1773 additions and 1736 deletions

View File

@@ -23,293 +23,225 @@ struct main0_in
float2 f_local_or_uv [[user(locn1)]];
float4 f_params [[user(locn2)]];
float4 f_params2 [[user(locn3)]];
uint f_kind_flags [[user(locn4)]];
float f_rotation [[user(locn5), flat]];
float4 f_uv_rect [[user(locn6), flat]];
uint f_flags [[user(locn4)]];
uint f_rotation_sc [[user(locn5)]];
uint4 f_uv_or_effects [[user(locn6)]];
};
static inline __attribute__((always_inline))
float2 apply_rotation(thread const float2& p, thread const float& angle)
float sdRoundedBox(thread const float2& p, thread const float2& b, thread const float4& r)
{
float cr = cos(-angle);
float sr = sin(-angle);
return float2x2(float2(cr, sr), float2(-sr, cr)) * p;
}
static inline __attribute__((always_inline))
float sdRoundedBox(thread const float2& p, thread const float2& b, thread float4& r)
{
float2 _61;
float2 _48;
if (p.x > 0.0)
{
_61 = r.xy;
_48 = r.xy;
}
else
{
_61 = r.zw;
_48 = r.zw;
}
r.x = _61.x;
r.y = _61.y;
float _78;
float2 rxy = _48;
float _62;
if (p.y > 0.0)
{
_78 = r.x;
_62 = rxy.x;
}
else
{
_78 = r.y;
_62 = rxy.y;
}
r.x = _78;
float2 q = (abs(p) - b) + float2(r.x);
return (fast::min(fast::max(q.x, q.y), 0.0) + length(fast::max(q, float2(0.0)))) - r.x;
}
static inline __attribute__((always_inline))
float sdf_stroke(thread const float& d, thread const float& stroke_width)
{
return abs(d) - (stroke_width * 0.5);
}
static inline __attribute__((always_inline))
float sdf_alpha(thread const float& d, thread const float& soft)
{
return 1.0 - smoothstep(-soft, soft, d);
}
static inline __attribute__((always_inline))
float sdCircle(thread const float2& p, thread const float& r)
{
return length(p) - r;
}
static inline __attribute__((always_inline))
float sdEllipse(thread float2& p, thread float2& ab)
{
p = abs(p);
if (p.x > p.y)
float rr = _62;
float2 q = abs(p) - b;
if (rr == 0.0)
{
p = p.yx;
ab = ab.yx;
return fast::max(q.x, q.y);
}
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) * 1.73205077648162841796875;
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_1 = ((2.0 * m) * n) * sqrt(d);
float s_1 = sign(q + h_1) * powr(abs(q + h_1), 0.3333333432674407958984375);
float u = sign(q - h_1) * powr(abs(q - h_1), 0.3333333432674407958984375);
float rx_1 = (((-s_1) - u) - (c * 4.0)) + (2.0 * m2);
float ry_1 = (s_1 - u) * 1.73205077648162841796875;
float rm = sqrt((rx_1 * rx_1) + (ry_1 * ry_1));
co = (((ry_1 / sqrt(rm - rx_1)) + ((2.0 * g) / rm)) - m) / 2.0;
}
float2 r = ab * float2(co, sqrt(1.0 - (co * co)));
return length(r - p) * sign(p.y - r.y);
q += float2(rr);
return (fast::min(fast::max(q.x, q.y), 0.0) + length(fast::max(q, float2(0.0)))) - rr;
}
static inline __attribute__((always_inline))
float sdSegment(thread const float2& p, thread const float2& a, thread const float2& b)
float sdRegularPolygon(thread const float2& p, thread const float& r, thread const float& n)
{
float2 pa = p - a;
float2 ba = b - a;
float h = fast::clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - (ba * h));
float an = 3.1415927410125732421875 / n;
float bn = mod(precise::atan2(p.y, p.x), 2.0 * an) - an;
return (length(p) * cos(bn)) - r;
}
static inline __attribute__((always_inline))
float sdEllipseApprox(thread const float2& p, thread const float2& ab)
{
float k0 = length(p / ab);
float k1 = length(p / (ab * ab));
return (k0 * (k0 - 1.0)) / k1;
}
static inline __attribute__((always_inline))
float4 gradient_2color(thread const float4& start_color, thread const float4& end_color, thread const float& t)
{
return mix(start_color, end_color, float4(fast::clamp(t, 0.0, 1.0)));
}
static inline __attribute__((always_inline))
float sdf_alpha(thread const float& d, thread const float& h)
{
return 1.0 - smoothstep(-h, h, d);
}
fragment main0_out main0(main0_in in [[stage_in]], texture2d<float> tex [[texture(0)]], sampler texSmplr [[sampler(0)]])
{
main0_out out = {};
uint kind = in.f_kind_flags & 255u;
uint flags = (in.f_kind_flags >> 8u) & 255u;
uint kind = in.f_flags & 255u;
uint flags = (in.f_flags >> 8u) & 255u;
if (kind == 0u)
{
out.out_color = in.f_color * tex.sample(texSmplr, in.f_local_or_uv);
float4 t = tex.sample(texSmplr, in.f_local_or_uv);
float _195 = t.w;
float4 _197 = t;
float3 _199 = _197.xyz * _195;
t.x = _199.x;
t.y = _199.y;
t.z = _199.z;
out.out_color = in.f_color * t;
return out;
}
float d = 1000000015047466219876688855040.0;
float soft = 1.0;
float h = 0.5;
float2 half_size = in.f_params.xy;
float2 p_local = in.f_local_or_uv;
if ((flags & 16u) != 0u)
{
float2 sc = float2(as_type<half2>(in.f_rotation_sc));
p_local = float2((sc.y * p_local.x) + (sc.x * p_local.y), ((-sc.x) * p_local.x) + (sc.y * p_local.y));
}
if (kind == 1u)
{
float2 b = in.f_params.xy;
float4 r = float4(in.f_params.zw, in.f_params2.xy);
soft = fast::max(in.f_params2.z, 1.0);
float stroke_px = in.f_params2.w;
float2 p_local = in.f_local_or_uv;
if (in.f_rotation != 0.0)
{
float2 param = p_local;
float param_1 = in.f_rotation;
p_local = apply_rotation(param, param_1);
}
float2 param_2 = p_local;
float2 param_3 = b;
float4 param_4 = r;
float _491 = sdRoundedBox(param_2, param_3, param_4);
d = _491;
if ((flags & 1u) != 0u)
{
float param_5 = d;
float param_6 = stroke_px;
d = sdf_stroke(param_5, param_6);
}
float4 shape_color = in.f_color;
if ((flags & 2u) != 0u)
{
float2 p_for_uv = in.f_local_or_uv;
if (in.f_rotation != 0.0)
{
float2 param_7 = p_for_uv;
float param_8 = in.f_rotation;
p_for_uv = apply_rotation(param_7, param_8);
}
float2 local_uv = ((p_for_uv / b) * 0.5) + float2(0.5);
float2 uv = mix(in.f_uv_rect.xy, in.f_uv_rect.zw, local_uv);
shape_color *= tex.sample(texSmplr, uv);
}
float param_9 = d;
float param_10 = soft;
float alpha = sdf_alpha(param_9, param_10);
out.out_color = float4(shape_color.xyz, shape_color.w * alpha);
return out;
float4 corner_radii = float4(in.f_params.zw, in.f_params2.xy);
h = in.f_params2.z;
float2 param = p_local;
float2 param_1 = half_size;
float4 param_2 = corner_radii;
d = sdRoundedBox(param, param_1, param_2);
}
else
{
if (kind == 2u)
{
float radius = in.f_params.x;
soft = fast::max(in.f_params.y, 1.0);
float stroke_px_1 = in.f_params.z;
float2 param_11 = in.f_local_or_uv;
float param_12 = radius;
d = sdCircle(param_11, param_12);
if ((flags & 1u) != 0u)
{
float param_13 = d;
float param_14 = stroke_px_1;
d = sdf_stroke(param_13, param_14);
}
float sides = in.f_params.y;
h = in.f_params.z;
float2 param_3 = p_local;
float param_4 = radius;
float param_5 = sides;
d = sdRegularPolygon(param_3, param_4, param_5);
half_size = float2(radius);
}
else
{
if (kind == 3u)
{
float2 ab = in.f_params.xy;
soft = fast::max(in.f_params.z, 1.0);
float stroke_px_2 = in.f_params.w;
float2 p_local_1 = in.f_local_or_uv;
if (in.f_rotation != 0.0)
{
float2 param_15 = p_local_1;
float param_16 = in.f_rotation;
p_local_1 = apply_rotation(param_15, param_16);
}
float2 param_17 = p_local_1;
float2 param_18 = ab;
float _616 = sdEllipse(param_17, param_18);
d = _616;
if ((flags & 1u) != 0u)
{
float param_19 = d;
float param_20 = stroke_px_2;
d = sdf_stroke(param_19, param_20);
}
h = in.f_params.z;
float2 param_6 = p_local;
float2 param_7 = ab;
d = sdEllipseApprox(param_6, param_7);
half_size = ab;
}
else
{
if (kind == 4u)
{
float2 a = in.f_params.xy;
float2 b_1 = in.f_params.zw;
float width = in.f_params2.x;
soft = fast::max(in.f_params2.y, 1.0);
float2 param_21 = in.f_local_or_uv;
float2 param_22 = a;
float2 param_23 = b_1;
d = sdSegment(param_21, param_22, param_23) - (width * 0.5);
}
else
{
if (kind == 5u)
float inner = in.f_params.x;
float outer = in.f_params.y;
float2 n_start = in.f_params.zw;
float2 n_end = in.f_params2.xy;
uint arc_bits = (flags >> 5u) & 3u;
h = in.f_params2.z;
float r = length(p_local);
d = fast::max(inner - r, r - outer);
if (arc_bits != 0u)
{
float inner = in.f_params.x;
float outer = in.f_params.y;
float start_rad = in.f_params.z;
float end_rad = in.f_params.w;
soft = fast::max(in.f_params2.x, 1.0);
float r_1 = length(in.f_local_or_uv);
float d_ring = fast::max(inner - r_1, r_1 - outer);
float angle = precise::atan2(in.f_local_or_uv.y, in.f_local_or_uv.x);
if (angle < 0.0)
float d_start = dot(p_local, n_start);
float d_end = dot(p_local, n_end);
float _372;
if (arc_bits == 1u)
{
angle += 6.283185482025146484375;
}
float ang_start = mod(start_rad, 6.283185482025146484375);
float ang_end = mod(end_rad, 6.283185482025146484375);
float _710;
if (ang_end > ang_start)
{
_710 = float((angle >= ang_start) && (angle <= ang_end));
_372 = fast::max(d_start, d_end);
}
else
{
_710 = float((angle >= ang_start) || (angle <= ang_end));
}
float in_arc = _710;
if (abs(ang_end - ang_start) >= 6.282185077667236328125)
{
in_arc = 1.0;
}
d = (in_arc > 0.5) ? d_ring : 1000000015047466219876688855040.0;
}
else
{
if (kind == 6u)
{
float radius_1 = in.f_params.x;
float rotation = in.f_params.y;
float sides = in.f_params.z;
soft = fast::max(in.f_params.w, 1.0);
float stroke_px_3 = in.f_params2.x;
float2 p = in.f_local_or_uv;
float c = cos(rotation);
float s = sin(rotation);
p = float2x2(float2(c, -s), float2(s, c)) * p;
float an = 3.1415927410125732421875 / sides;
float bn = mod(precise::atan2(p.y, p.x), 2.0 * an) - an;
d = (length(p) * cos(bn)) - radius_1;
if ((flags & 1u) != 0u)
{
float param_24 = d;
float param_25 = stroke_px_3;
d = sdf_stroke(param_24, param_25);
}
_372 = fast::min(d_start, d_end);
}
float d_wedge = _372;
d = fast::max(d, d_wedge);
}
half_size = float2(outer);
}
}
}
}
float param_26 = d;
float param_27 = soft;
float alpha_1 = sdf_alpha(param_26, param_27);
out.out_color = float4(in.f_color.xyz, in.f_color.w * alpha_1);
float grad_magnitude = fast::max(fwidth(d), 9.9999999747524270787835121154785e-07);
d /= grad_magnitude;
h /= grad_magnitude;
float4 shape_color;
if ((flags & 2u) != 0u)
{
float4 gradient_start = in.f_color;
float4 gradient_end = unpack_unorm4x8_to_float(in.f_uv_or_effects.x);
if ((flags & 4u) != 0u)
{
float t_1 = length(p_local / half_size);
float4 param_8 = gradient_start;
float4 param_9 = gradient_end;
float param_10 = t_1;
shape_color = gradient_2color(param_8, param_9, param_10);
}
else
{
float2 direction = float2(as_type<half2>(in.f_uv_or_effects.z));
float t_2 = (dot(p_local / half_size, direction) * 0.5) + 0.5;
float4 param_11 = gradient_start;
float4 param_12 = gradient_end;
float param_13 = t_2;
shape_color = gradient_2color(param_11, param_12, param_13);
}
}
else
{
if ((flags & 1u) != 0u)
{
float4 uv_rect = as_type<float4>(in.f_uv_or_effects);
float2 local_uv = ((p_local / half_size) * 0.5) + float2(0.5);
float2 uv = mix(uv_rect.xy, uv_rect.zw, local_uv);
shape_color = in.f_color * tex.sample(texSmplr, uv);
}
else
{
shape_color = in.f_color;
}
}
if ((flags & 8u) != 0u)
{
float4 ol_color = unpack_unorm4x8_to_float(in.f_uv_or_effects.y);
float ol_width = float2(as_type<half2>(in.f_uv_or_effects.w)).x / grad_magnitude;
float param_14 = d;
float param_15 = h;
float fill_cov = sdf_alpha(param_14, param_15);
float param_16 = d - ol_width;
float param_17 = h;
float total_cov = sdf_alpha(param_16, param_17);
float outline_cov = fast::max(total_cov - fill_cov, 0.0);
float3 rgb_pm = ((shape_color.xyz * shape_color.w) * fill_cov) + ((ol_color.xyz * ol_color.w) * outline_cov);
float alpha_pm = (shape_color.w * fill_cov) + (ol_color.w * outline_cov);
out.out_color = float4(rgb_pm, alpha_pm);
}
else
{
float param_18 = d;
float param_19 = h;
float alpha = sdf_alpha(param_18, param_19);
out.out_color = float4((shape_color.xyz * shape_color.w) * alpha, shape_color.w * alpha);
}
return out;
}

View File

@@ -14,24 +14,24 @@ struct Primitive
{
float4 bounds;
uint color;
uint kind_flags;
float rotation;
uint flags;
uint rotation_sc;
float _pad;
float4 params;
float4 params2;
float4 uv_rect;
uint4 uv_or_effects;
};
struct Primitive_1
{
float4 bounds;
uint color;
uint kind_flags;
float rotation;
uint flags;
uint rotation_sc;
float _pad;
float4 params;
float4 params2;
float4 uv_rect;
uint4 uv_or_effects;
};
struct Primitives
@@ -45,9 +45,9 @@ struct main0_out
float2 f_local_or_uv [[user(locn1)]];
float4 f_params [[user(locn2)]];
float4 f_params2 [[user(locn3)]];
uint f_kind_flags [[user(locn4)]];
float f_rotation [[user(locn5)]];
float4 f_uv_rect [[user(locn6)]];
uint f_flags [[user(locn4)]];
uint f_rotation_sc [[user(locn5)]];
uint4 f_uv_or_effects [[user(locn6)]];
float4 gl_Position [[position]];
};
@@ -58,7 +58,7 @@ struct main0_in
float4 v_color [[attribute(2)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant Uniforms& _12 [[buffer(0)]], const device Primitives& _74 [[buffer(1)]], uint gl_InstanceIndex [[instance_id]])
vertex main0_out main0(main0_in in [[stage_in]], constant Uniforms& _12 [[buffer(0)]], const device Primitives& _75 [[buffer(1)]], uint gl_InstanceIndex [[instance_id]])
{
main0_out out = {};
if (_12.mode == 0u)
@@ -67,22 +67,22 @@ vertex main0_out main0(main0_in in [[stage_in]], constant Uniforms& _12 [[buffer
out.f_local_or_uv = in.v_uv;
out.f_params = float4(0.0);
out.f_params2 = float4(0.0);
out.f_kind_flags = 0u;
out.f_rotation = 0.0;
out.f_uv_rect = float4(0.0, 0.0, 1.0, 1.0);
out.f_flags = 0u;
out.f_rotation_sc = 0u;
out.f_uv_or_effects = uint4(0u);
out.gl_Position = _12.projection * float4(in.v_position * _12.dpi_scale, 0.0, 1.0);
}
else
{
Primitive p;
p.bounds = _74.primitives[int(gl_InstanceIndex)].bounds;
p.color = _74.primitives[int(gl_InstanceIndex)].color;
p.kind_flags = _74.primitives[int(gl_InstanceIndex)].kind_flags;
p.rotation = _74.primitives[int(gl_InstanceIndex)].rotation;
p._pad = _74.primitives[int(gl_InstanceIndex)]._pad;
p.params = _74.primitives[int(gl_InstanceIndex)].params;
p.params2 = _74.primitives[int(gl_InstanceIndex)].params2;
p.uv_rect = _74.primitives[int(gl_InstanceIndex)].uv_rect;
p.bounds = _75.primitives[int(gl_InstanceIndex)].bounds;
p.color = _75.primitives[int(gl_InstanceIndex)].color;
p.flags = _75.primitives[int(gl_InstanceIndex)].flags;
p.rotation_sc = _75.primitives[int(gl_InstanceIndex)].rotation_sc;
p._pad = _75.primitives[int(gl_InstanceIndex)]._pad;
p.params = _75.primitives[int(gl_InstanceIndex)].params;
p.params2 = _75.primitives[int(gl_InstanceIndex)].params2;
p.uv_or_effects = _75.primitives[int(gl_InstanceIndex)].uv_or_effects;
float2 corner = in.v_position;
float2 world_pos = mix(p.bounds.xy, p.bounds.zw, corner);
float2 center = (p.bounds.xy + p.bounds.zw) * 0.5;
@@ -90,10 +90,11 @@ vertex main0_out main0(main0_in in [[stage_in]], constant Uniforms& _12 [[buffer
out.f_local_or_uv = (world_pos - center) * _12.dpi_scale;
out.f_params = p.params;
out.f_params2 = p.params2;
out.f_kind_flags = p.kind_flags;
out.f_rotation = p.rotation;
out.f_uv_rect = p.uv_rect;
out.f_flags = p.flags;
out.f_rotation_sc = p.rotation_sc;
out.f_uv_or_effects = p.uv_or_effects;
out.gl_Position = _12.projection * float4(world_pos * _12.dpi_scale, 0.0, 1.0);
}
return out;
}

View File

@@ -1,13 +1,13 @@
#version 450 core
// --- Inputs from vertex shader ---
layout(location = 0) in vec4 f_color;
layout(location = 0) in mediump 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;
layout(location = 6) flat in vec4 f_uv_rect;
layout(location = 4) flat in uint f_flags;
layout(location = 5) flat in uint f_rotation_sc;
layout(location = 6) flat in uvec4 f_uv_or_effects;
// --- Output ---
layout(location = 0) out vec4 out_color;
@@ -20,77 +20,43 @@ layout(set = 2, binding = 0) uniform sampler2D tex;
// 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;
vec2 rxy = (p.x > 0.0) ? r.xy : r.zw;
float rr = (p.y > 0.0) ? rxy.x : rxy.y;
vec2 q = abs(p) - b;
if (rr == 0.0) {
return max(q.x, q.y);
}
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);
q += rr;
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr;
}
float sdf_alpha(float d, float soft) {
return 1.0 - smoothstep(-soft, soft, d);
// Approximate ellipse SDF — fast, suitable for UI, NOT a true Euclidean distance.
float sdEllipseApprox(vec2 p, vec2 ab) {
float k0 = length(p / ab);
float k1 = length(p / (ab * ab));
return k0 * (k0 - 1.0) / k1;
}
float sdf_stroke(float d, float stroke_width) {
return abs(d) - stroke_width * 0.5;
// Regular N-gon SDF (Inigo Quilez).
float sdRegularPolygon(vec2 p, float r, float n) {
float an = 3.141592653589793 / n;
float bn = mod(atan(p.y, p.x), 2.0 * an) - an;
return length(p) * cos(bn) - r;
}
// 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;
// Coverage from SDF distance using half-feather width (feather_px * 0.5, pre-computed on CPU).
// Produces a symmetric transition centered on d=0: smoothstep(-h, h, d).
float sdf_alpha(float d, float h) {
return 1.0 - smoothstep(-h, h, d);
}
// ---------------------------------------------------------------------------
// Gradient helpers
// ---------------------------------------------------------------------------
mediump vec4 gradient_2color(mediump vec4 start_color, mediump vec4 end_color, mediump float t) {
return mix(start_color, end_color, clamp(t, 0.0, 1.0));
}
// ---------------------------------------------------------------------------
@@ -98,131 +64,137 @@ vec2 apply_rotation(vec2 p, float angle) {
// ---------------------------------------------------------------------------
void main() {
uint kind = f_kind_flags & 0xFFu;
uint flags = (f_kind_flags >> 8u) & 0xFFu;
uint kind = f_flags & 0xFFu;
uint flags = (f_flags >> 8u) & 0xFFu;
// -----------------------------------------------------------------------
// Kind 0: Tessellated path. Texture multiply for text atlas,
// white pixel for solid shapes.
// -----------------------------------------------------------------------
// Kind 0: Tessellated path — vertex colors arrive premultiplied from CPU.
// Texture samples are straight-alpha (SDL_ttf glyph atlas: rgb=1, a=coverage;
// or the 1x1 white texture: rgba=1). Convert to premultiplied form so the
// blend state (ONE, ONE_MINUS_SRC_ALPHA) composites correctly.
if (kind == 0u) {
out_color = f_color * texture(tex, f_local_or_uv);
vec4 t = texture(tex, f_local_or_uv);
t.rgb *= t.a;
out_color = f_color * t;
return;
}
// -----------------------------------------------------------------------
// SDF path. f_local_or_uv = shape-centered position in physical pixels.
// All dimensional params are already in physical pixels (CPU pre-scaled).
// -----------------------------------------------------------------------
// SDF path — dispatch on kind
float d = 1e30;
float soft = 1.0;
float h = 0.5; // half-feather width; overwritten per shape kind
vec2 half_size = f_params.xy; // used by RRect and as reference size for gradients
vec2 p_local = f_local_or_uv;
// Apply inverse rotation using pre-computed sin/cos (no per-pixel trig).
// .Rotated flag = bit 4 = 16u
if ((flags & 16u) != 0u) {
vec2 sc = unpackHalf2x16(f_rotation_sc); // .x = sin(angle), .y = cos(angle)
// Inverse rotation matrix R(-angle) = [[cos, sin], [-sin, cos]]
p_local = vec2(sc.y * p_local.x + sc.x * p_local.y,
-sc.x * p_local.x + sc.y * p_local.y);
}
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);
// Texture sampling for textured SDF primitives
vec4 shape_color = f_color;
if ((flags & 2u) != 0u) {
// Compute UV from local position and half_size
vec2 p_for_uv = f_local_or_uv;
if (f_rotation != 0.0) {
p_for_uv = apply_rotation(p_for_uv, f_rotation);
}
vec2 local_uv = p_for_uv / b * 0.5 + 0.5;
vec2 uv = mix(f_uv_rect.xy, f_uv_rect.zw, local_uv);
shape_color *= texture(tex, uv);
}
float alpha = sdf_alpha(d, soft);
out_color = vec4(shape_color.rgb, shape_color.a * alpha);
return;
// RRect — half_feather in params2.z
vec4 corner_radii = vec4(f_params.zw, f_params2.xy);
h = f_params2.z;
d = sdRoundedBox(p_local, half_size, corner_radii);
}
else if (kind == 2u) {
// Circle — rotationally symmetric, no rotation needed
// NGon — half_feather in params.z
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);
float sides = f_params.y;
h = f_params.z;
d = sdRegularPolygon(p_local, radius, sides);
half_size = vec2(radius); // for gradient UV computation
}
else if (kind == 3u) {
// Ellipse
// Ellipse — half_feather in params.z
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);
h = f_params.z;
d = sdEllipseApprox(p_local, ab);
half_size = ab; // for gradient UV computation
}
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
// Ring_Arc — half_feather in params2.z
// Arc mode from flag bits 5-6: 0 = full, 1 = narrow (≤π), 2 = wide (>π)
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);
vec2 n_start = f_params.zw;
vec2 n_end = f_params2.xy;
uint arc_bits = (flags >> 5u) & 3u;
float r = length(f_local_or_uv);
float d_ring = max(inner - r, r - outer);
h = f_params2.z;
// 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 r = length(p_local);
d = max(inner - r, r - outer);
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;
if (arc_bits != 0u) {
float d_start = dot(p_local, n_start);
float d_end = dot(p_local, n_end);
float d_wedge = (arc_bits == 1u)
? max(d_start, d_end) // arc ≤ π: intersect half-planes
: min(d_start, d_end); // arc > π: union half-planes
d = max(d, d_wedge);
}
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);
half_size = vec2(outer); // for gradient UV computation
}
float alpha = sdf_alpha(d, soft);
out_color = vec4(f_color.rgb, f_color.a * alpha);
// --- fwidth-based normalization for correct AA and stroke width ---
float grad_magnitude = max(fwidth(d), 1e-6);
d = d / grad_magnitude;
h = h / grad_magnitude;
// --- Determine shape color based on flags ---
mediump vec4 shape_color;
if ((flags & 2u) != 0u) {
// Gradient active (bit 1)
mediump vec4 gradient_start = f_color;
mediump vec4 gradient_end = unpackUnorm4x8(f_uv_or_effects.x);
if ((flags & 4u) != 0u) {
// Radial gradient (bit 2): t from distance to center
mediump float t = length(p_local / half_size);
shape_color = gradient_2color(gradient_start, gradient_end, t);
} else {
// Linear gradient: direction pre-computed on CPU as (cos, sin) f16 pair
vec2 direction = unpackHalf2x16(f_uv_or_effects.z);
mediump float t = dot(p_local / half_size, direction) * 0.5 + 0.5;
shape_color = gradient_2color(gradient_start, gradient_end, t);
}
} else if ((flags & 1u) != 0u) {
// Textured (bit 0) — RRect only in practice
vec4 uv_rect = uintBitsToFloat(f_uv_or_effects);
vec2 local_uv = p_local / half_size * 0.5 + 0.5;
vec2 uv = mix(uv_rect.xy, uv_rect.zw, local_uv);
shape_color = f_color * texture(tex, uv);
} else {
// Solid color
shape_color = f_color;
}
// --- Outline (bit 3) — outer outline via premultiplied compositing ---
// The outline band sits OUTSIDE the original shape boundary (d=0 to d=+ol_width).
// fill_cov covers the interior with AA at d=0; total_cov covers interior+outline with
// AA at d=ol_width. The outline band's coverage is total_cov - fill_cov.
// Output is premultiplied: blend state is ONE, ONE_MINUS_SRC_ALPHA.
if ((flags & 8u) != 0u) {
mediump vec4 ol_color = unpackUnorm4x8(f_uv_or_effects.y);
// Outline width in f_uv_or_effects.w (low f16 half)
float ol_width = unpackHalf2x16(f_uv_or_effects.w).x / grad_magnitude;
float fill_cov = sdf_alpha(d, h);
float total_cov = sdf_alpha(d - ol_width, h);
float outline_cov = max(total_cov - fill_cov, 0.0);
// Premultiplied output — no divide, no threshold check
vec3 rgb_pm = shape_color.rgb * shape_color.a * fill_cov
+ ol_color.rgb * ol_color.a * outline_cov;
float alpha_pm = shape_color.a * fill_cov + ol_color.a * outline_cov;
out_color = vec4(rgb_pm, alpha_pm);
} else {
mediump float alpha = sdf_alpha(d, h);
out_color = vec4(shape_color.rgb * shape_color.a * alpha, shape_color.a * alpha);
}
}

View File

@@ -6,13 +6,13 @@ layout(location = 1) in vec2 v_uv;
layout(location = 2) in vec4 v_color;
// ---------- Outputs to fragment shader ----------
layout(location = 0) out vec4 f_color;
layout(location = 0) out mediump vec4 f_color;
layout(location = 1) out vec2 f_local_or_uv;
layout(location = 2) out vec4 f_params;
layout(location = 3) out vec4 f_params2;
layout(location = 4) flat out uint f_kind_flags;
layout(location = 5) flat out float f_rotation;
layout(location = 6) flat out vec4 f_uv_rect;
layout(location = 4) flat out uint f_flags;
layout(location = 5) flat out uint f_rotation_sc;
layout(location = 6) flat out uvec4 f_uv_or_effects;
// ---------- Uniforms (single block — avoids spirv-cross reordering on Metal) ----------
layout(set = 1, binding = 0) uniform Uniforms {
@@ -23,14 +23,14 @@ layout(set = 1, binding = 0) uniform Uniforms {
// ---------- SDF primitive storage buffer ----------
struct Primitive {
vec4 bounds; // 0-15: min_x, min_y, max_x, max_y
uint color; // 16-19: packed u8x4 (unpack with unpackUnorm4x8)
uint kind_flags; // 20-23: kind | (flags << 8)
float rotation; // 24-27: shader self-rotation in radians
float _pad; // 28-31: alignment padding
vec4 params; // 32-47: shape params part 1
vec4 params2; // 48-63: shape params part 2
vec4 uv_rect; // 64-79: u_min, v_min, u_max, v_max
vec4 bounds; // 0-15
uint color; // 16-19
uint flags; // 20-23
uint rotation_sc; // 24-27: packed f16 pair (sin, cos)
float _pad; // 28-31
vec4 params; // 32-47
vec4 params2; // 48-63
uvec4 uv_or_effects; // 64-79
};
layout(std430, set = 0, binding = 0) readonly buffer Primitives {
@@ -45,9 +45,9 @@ void main() {
f_local_or_uv = v_uv;
f_params = vec4(0.0);
f_params2 = vec4(0.0);
f_kind_flags = 0u;
f_rotation = 0.0;
f_uv_rect = vec4(0.0, 0.0, 1.0, 1.0);
f_flags = 0u;
f_rotation_sc = 0u;
f_uv_or_effects = uvec4(0);
gl_Position = projection * vec4(v_position * dpi_scale, 0.0, 1.0);
} else {
@@ -62,9 +62,9 @@ void main() {
f_local_or_uv = (world_pos - center) * dpi_scale; // shape-centered physical pixels
f_params = p.params;
f_params2 = p.params2;
f_kind_flags = p.kind_flags;
f_rotation = p.rotation;
f_uv_rect = p.uv_rect;
f_flags = p.flags;
f_rotation_sc = p.rotation_sc;
f_uv_or_effects = p.uv_or_effects;
gl_Position = projection * vec4(world_pos * dpi_scale, 0.0, 1.0);
}