159 lines
5.0 KiB
WebGPU Shading Language
159 lines
5.0 KiB
WebGPU Shading Language
// Text pass: Slug algorithm ported from JSlug (GLSL3 → WGSL).
|
|
// Evaluates TrueType quadratic Bézier curves directly in the fragment
|
|
// shader for resolution-independent, anti-aliased text rendering.
|
|
// No glyph atlas — curves are packed into a float texture, spatially
|
|
// banded into a uint texture, and ray-traced per fragment.
|
|
|
|
const TEXTURE_WIDTH = 4096u;
|
|
const EPSILON = 0.0001;
|
|
|
|
struct TextUniforms {
|
|
resolution: vec2f,
|
|
textColor: vec4f,
|
|
}
|
|
|
|
@group(0) @binding(0) var<uniform> u: TextUniforms;
|
|
@group(0) @binding(1) var curvesTex: texture_2d<f32>;
|
|
@group(0) @binding(2) var bandsTex: texture_2d<u32>;
|
|
|
|
struct VsIn {
|
|
@location(0) position: vec2f,
|
|
@location(1) aScaleBias: vec4f,
|
|
@location(2) aGlyphBandScale: vec4f,
|
|
@location(3) aBandMaxTexCoords: vec4u,
|
|
}
|
|
|
|
struct VsOut {
|
|
@builtin(position) pos: vec4f,
|
|
@location(0) vTexCoords: vec2f,
|
|
@interpolate(flat) @location(1) vGlyphBandScale: vec4f,
|
|
@interpolate(flat) @location(2) vBandMaxTexCoords: vec4u,
|
|
@interpolate(flat) @location(3) vPixelsPerEm: vec2f,
|
|
}
|
|
|
|
@vertex
|
|
fn vs_main(in: VsIn) -> VsOut {
|
|
let px = in.position.x * in.aScaleBias.x + in.aScaleBias.z;
|
|
let py = in.position.y * in.aScaleBias.y + in.aScaleBias.w;
|
|
var out: VsOut;
|
|
out.pos = vec4f(
|
|
px / u.resolution.x * 2.0 - 1.0,
|
|
py / u.resolution.y * 2.0 - 1.0,
|
|
0.0, 1.0
|
|
);
|
|
out.vTexCoords = in.position * 0.5 + vec2f(0.5);
|
|
out.vGlyphBandScale = in.aGlyphBandScale;
|
|
out.vBandMaxTexCoords = in.aBandMaxTexCoords;
|
|
let ppe = vec2f(2.0 * in.aScaleBias.x, 2.0 * in.aScaleBias.y);
|
|
out.vPixelsPerEm = clamp(ppe, vec2f(1.0), vec2f(200.0));
|
|
return out;
|
|
}
|
|
|
|
// ---------- Slug ray tracer ----------
|
|
|
|
fn trace_ray_curve_h(p1: vec2f, p2: vec2f, p3: vec2f, pixels_per_em: f32) -> f32 {
|
|
if (max(max(p1.x, p2.x), p3.x) * pixels_per_em < -0.5) {
|
|
return 0.0;
|
|
}
|
|
|
|
let y_flags = select(0u, 2u, p1.y > 0.0)
|
|
+ select(0u, 4u, p2.y > 0.0)
|
|
+ select(0u, 8u, p3.y > 0.0);
|
|
let code = (0x2E74u >> y_flags) & 3u;
|
|
if (code == 0u) {
|
|
return 0.0;
|
|
}
|
|
|
|
let a = p1 - p2 * 2.0 + p3;
|
|
let b = p1 - p2;
|
|
let c = p1.y;
|
|
let ayr = 1.0 / a.y;
|
|
let disc = max(b.y * b.y - a.y * c, 0.0);
|
|
let d = sqrt(disc);
|
|
var t1 = (b.y - d) * ayr;
|
|
var t2 = (b.y + d) * ayr;
|
|
|
|
if (abs(a.y) < EPSILON) {
|
|
t1 = c / (2.0 * b.y);
|
|
t2 = t1;
|
|
}
|
|
|
|
var coverage = 0.0;
|
|
|
|
if ((code & 1u) != 0u) {
|
|
let x1 = (a.x * t1 - b.x * 2.0) * t1 + p1.x;
|
|
coverage += clamp(x1 * pixels_per_em + 0.5, 0.0, 1.0);
|
|
}
|
|
|
|
if (code > 1u) {
|
|
let x2 = (a.x * t2 - b.x * 2.0) * t2 + p1.x;
|
|
coverage -= clamp(x2 * pixels_per_em + 0.5, 0.0, 1.0);
|
|
}
|
|
|
|
return coverage;
|
|
}
|
|
|
|
fn trace_ray_band_h(
|
|
band_data: vec2u,
|
|
pixels_per_em: f32,
|
|
v_tex: vec2f,
|
|
glyph_scale: vec2f
|
|
) -> f32 {
|
|
var coverage = 0.0;
|
|
for (var curve = 0u; curve < band_data.x; curve++) {
|
|
let curve_offset = band_data.y + curve;
|
|
let curve_loc = textureLoad(bandsTex, vec2u(curve_offset & 0xFFFu, curve_offset >> 12u), 0).xy;
|
|
let p12 = textureLoad(curvesTex, curve_loc, 0) / vec4f(glyph_scale, glyph_scale) - vec4f(v_tex, v_tex);
|
|
let p3 = textureLoad(curvesTex, vec2u(curve_loc.x + 1u, curve_loc.y), 0).xy / glyph_scale - v_tex;
|
|
coverage += trace_ray_curve_h(p12.xy, p12.zw, p3.xy, pixels_per_em);
|
|
}
|
|
return coverage;
|
|
}
|
|
|
|
fn trace_ray_band_v(
|
|
band_data: vec2u,
|
|
pixels_per_em: f32,
|
|
v_tex: vec2f,
|
|
glyph_scale: vec2f
|
|
) -> f32 {
|
|
var coverage = 0.0;
|
|
for (var curve = 0u; curve < band_data.x; curve++) {
|
|
let curve_offset = band_data.y + curve;
|
|
let curve_loc = textureLoad(bandsTex, vec2u(curve_offset & 0xFFFu, curve_offset >> 12u), 0).xy;
|
|
let p12 = textureLoad(curvesTex, curve_loc, 0) / vec4f(glyph_scale, glyph_scale) - vec4f(v_tex, v_tex);
|
|
let p3 = textureLoad(curvesTex, vec2u(curve_loc.x + 1u, curve_loc.y), 0).xy / glyph_scale - v_tex;
|
|
coverage += trace_ray_curve_h(p12.yx, p12.wz, p3.yx, pixels_per_em);
|
|
}
|
|
return coverage;
|
|
}
|
|
|
|
@fragment
|
|
fn fs_main(in: VsOut) -> @location(0) vec4f {
|
|
let glyph_scale = in.vGlyphBandScale.xy;
|
|
let band_scale = in.vGlyphBandScale.zw;
|
|
let band_max = in.vBandMaxTexCoords.xy;
|
|
let bands_tex_coords = in.vBandMaxTexCoords.zw;
|
|
let v_tex = in.vTexCoords;
|
|
let pixels_per_em = in.vPixelsPerEm;
|
|
|
|
let band_index = clamp(
|
|
vec2u(v_tex * band_scale),
|
|
vec2u(0u, 0u),
|
|
band_max
|
|
);
|
|
|
|
let h_band_offset = bands_tex_coords.y * TEXTURE_WIDTH + bands_tex_coords.x + band_index.y;
|
|
let h_band_data = textureLoad(bandsTex, vec2u(h_band_offset & 0xFFFu, h_band_offset >> 12u), 0).xy;
|
|
|
|
let v_band_offset = bands_tex_coords.y * TEXTURE_WIDTH + bands_tex_coords.x + band_max.y + 1u + band_index.x;
|
|
let v_band_data = textureLoad(bandsTex, vec2u(v_band_offset & 0xFFFu, v_band_offset >> 12u), 0).xy;
|
|
|
|
var coverage_x = trace_ray_band_h(h_band_data, pixels_per_em.x, v_tex, glyph_scale);
|
|
var coverage_y = trace_ray_band_v(v_band_data, pixels_per_em.y, v_tex, glyph_scale);
|
|
|
|
coverage_x = min(abs(coverage_x), 1.0);
|
|
coverage_y = min(abs(coverage_y), 1.0);
|
|
let slug_alpha = (coverage_x + coverage_y) * 0.5;
|
|
|
|
return vec4f(u.textColor.rgb, u.textColor.a * slug_alpha);
|
|
} |