webgpu-app/font.wgsl

237 lines
6.2 KiB
WebGPU Shading Language

struct View {
viewProj: mat4x4f,
lightViewProj: mat4x4f,
lightPosition: vec4f,
eyePosition: vec4f,
aspect: f32,
};
struct Config {
mode: f32,
scale: f32,
translateX: f32,
translateY: f32,
distanceThreshold: f32,
distanceScale: f32,
aliasingEnable: f32,
rampMin: f32,
subpixelEnable: f32,
subpixelStrength: f32,
subpixelOffset: f32,
rampMax: f32,
supersampleCount: f32,
supersampleOffset: f32,
outlineThreshold: f32,
outlineScale: f32,
insideColor: vec4f,
outlineColor: vec4f,
};
struct GlyphBuffer {
texPosition: vec2f,
texSize: vec2f,
size: vec2f,
unused: vec2f,
};
struct LayoutBuffer {
position: vec4f, // glyph_index w
};
@group(0) @binding(0) var<uniform> view: View;
@group(1) @binding(0) var linearSampler: sampler;
@group(1) @binding(1) var fontTexture: texture_2d<f32>;
@group(1) @binding(2) var<storage> glyphBuffer: array<GlyphBuffer>;
@group(2) @binding(0) var<storage> layoutBuffer: array<LayoutBuffer>;
@group(2) @binding(1) var<uniform> config: Config;
struct VertexInput {
@builtin(vertex_index) vertex_index: u32,
@builtin(instance_index) instance_index: u32,
};
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) @interpolate(linear, sample) texture: vec2f,
//@location(0) texture: vec2f,
@location(1) @interpolate(flat) glyphIndex: u32,
};
const vertices = array(
vec2f(1.0, 0.0),
vec2f(0.0, 1.0),
vec2f(0.0, 0.0),
vec2f(1.0, 1.0),
);
@vertex
fn vertexMain(input: VertexInput) -> VertexOutput
{
let texture = vertices[input.vertex_index];
let position = vec2f(texture.x, 1.0 - texture.y);
let glyphIndex = u32(layoutBuffer[input.instance_index].position.w);
let viewPosition = position * glyphBuffer[glyphIndex].size + layoutBuffer[input.instance_index].position.xy;
let screenPosition = viewPosition * vec2f(1 / view.aspect, 1) * config.scale + vec2f(config.translateX, config.translateY);
var output: VertexOutput;
output.position = vec4f(screenPosition, 0.0, 1.0);
output.texture = texture;
output.glyphIndex = glyphIndex;
return output;
}
fn palette(t: f32) -> vec4f
{
let a = vec3f(0.5, 0.5, 0.5);
let b = vec3f(0.5, 0.5, 0.5);
let c = vec3f(1.0, 1.0, 1.0);
let d = vec3f(0.00, 0.33, 0.67);
//0.263,0.416,0.557
let color = a + b * cos(6.28318 * (c * t + d));
return vec4f(color, 1);
}
fn aliasedLinearSaturation(distance: f32, threshold: f32, scale: f32) -> f32
{
let distanceFromEdge = threshold - distance;
let dx = dpdx(distanceFromEdge);
let dy = dpdy(distanceFromEdge);
let gradientLength = length(vec2f(dx, dy));
let thresholdWidth = scale * gradientLength;
let aliased = saturate((distanceFromEdge / thresholdWidth) + 0.5);
return 1.0 - aliased;
}
struct FragmentOutput {
@location(0) color: vec4f,
};
fn sampleAt(texture: vec2f) -> f32
{
let base = textureSample(fontTexture, linearSampler, texture);
let distance = base.x;
var t: f32;
if (config.aliasingEnable == 1.0) {
t = aliasedLinearSaturation(distance, config.distanceThreshold, config.distanceScale);
} else {
t = f32(distance > config.distanceThreshold);
}
return t;
}
fn sampleAt2(texture: vec2f,
threshold1: f32, scale1: f32,
threshold2: f32, scale2: f32) -> vec2f
{
let base = textureSample(fontTexture, linearSampler, texture);
let distance = base.x;
var t: vec2f;
if (config.aliasingEnable == 1.0) {
t.x = aliasedLinearSaturation(distance, threshold1, scale1);
t.y = aliasedLinearSaturation(distance, threshold2, scale2);
} else {
t.x = f32(distance > threshold1);
t.y = f32(distance > threshold2);
}
return t;
}
const offsets = array(
vec2f(0.125, 0.375),
vec2f(-0.125, -0.375),
vec2f(0.375, -0.125),
vec2f(-0.375, 0.125),
);
fn supersampleColor(texture: vec2f, s: vec2f, p: vec2f) -> vec4f
{
let insideColor = vec4f(config.insideColor.xyz, 1);
let outlineColor = vec4f(config.outlineColor.xyz, 1);
let outsideColor = vec4f(0, 0, 0, 0);
let dx = dpdx(texture.x * s.x);
let dy = dpdy(texture.y * s.y);
let fdim = config.supersampleCount;
let idim = i32(floor(fdim / 2.0));
var t = vec2f(0, 0);
if (fdim == 1) {
let coordinate = texture * s + p;
t = sampleAt2(coordinate,
config.distanceThreshold, config.distanceScale,
config.outlineThreshold, config.outlineScale);
} else {
for (var y = -idim; y <= idim; y++) {
for (var x = -idim; x <= idim; x++) {
//for (var i = 0; i < 4; i++) {
//let offset = vec2f(dx, dy) * offsets[i] * config.supersampleOffset;
let offset = vec2f(dx, dy) * vec2f(f32(x), f32(y)) * 0.1 * config.supersampleOffset;
let coordinate = texture * s + offset + p;
t += sampleAt2(coordinate,
config.distanceThreshold, config.distanceScale,
config.outlineThreshold, config.outlineScale);
//}
}
}
t *= (1.0 / (fdim * fdim));
}
let color = mix(outsideColor, outlineColor, t.y);
return mix(color, insideColor, t.x);
}
fn subpixelColor(texture: vec2f, s: vec2f, p: vec2f) -> vec4f
{
let dx = dpdx(texture.x * s.x * 1/3);
let dy = dpdy(texture.y * s.y);
var subpixelColor = vec3f(0, 0, 0);
let insideColor = vec4f(1, 1, 1, 1);
let outsideColor = vec4f(0, 0, 0, 0);
if (config.subpixelEnable == 0) {
let coordinate = texture * s + p;
let t = sampleAt(coordinate);
return mix(outsideColor, insideColor, t);
} else {
for (var x = -1; x <= 1; x += 1) {
let offset = vec2f(dx * f32(x) * config.subpixelOffset, 0);
let coordinate = texture * s + offset + p;
subpixelColor[(x + 1)] = sampleAt(coordinate);
}
let mono = (subpixelColor.x + subpixelColor.y + subpixelColor.z) / 3;
let subpixelMix = mix(vec3f(mono, mono, mono), subpixelColor.xyz, config.subpixelStrength);
return vec4f(subpixelMix, length(subpixelMix));
}
}
@fragment
fn fragmentMain(input: VertexOutput) -> FragmentOutput
{
let s = glyphBuffer[input.glyphIndex].texSize;
let p = glyphBuffer[input.glyphIndex].texPosition;
var output: FragmentOutput;
if (config.mode == 1) {
output.color = subpixelColor(input.texture, s, p);
} else {
output.color = supersampleColor(input.texture, s, p);
}
return output;
}