#version 450 core // --- Inputs from vertex shader --- 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_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; // --- 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. // --------------------------------------------------------------------------- float sdRoundedBox(vec2 p, vec2 b, vec4 r) { 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); } q += rr; return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; } // 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; } // 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; } // 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)); } // --------------------------------------------------------------------------- // main // --------------------------------------------------------------------------- void main() { uint kind = f_flags & 0xFFu; uint flags = (f_flags >> 8u) & 0xFFu; // 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) { vec4 t = texture(tex, f_local_or_uv); t.rgb *= t.a; out_color = f_color * t; return; } // SDF path — dispatch on kind float d = 1e30; 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 — 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) { // NGon — half_feather in params.z float radius = f_params.x; 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 — half_feather in params.z vec2 ab = f_params.xy; h = f_params.z; d = sdEllipseApprox(p_local, ab); half_size = ab; // for gradient UV computation } else if (kind == 4u) { // 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; vec2 n_start = f_params.zw; vec2 n_end = f_params2.xy; uint arc_bits = (flags >> 5u) & 3u; h = f_params2.z; float r = length(p_local); d = max(inner - r, r - outer); 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); } half_size = vec2(outer); // for gradient UV computation } // --- 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); } }