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 view: View; @group(1) @binding(0) var linearSampler: sampler; @group(1) @binding(1) var fontTexture: texture_2d; @group(1) @binding(2) var glyphBuffer: array; @group(2) @binding(0) var layoutBuffer: array; @group(2) @binding(1) var 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; }