Files
webgpu-portfolio/src/engine/text/text.wgsl

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);
}