struct TextInstanceData {
color: vec4<f32>,
outline_color: vec4<f32>,
clip_rect: vec4<f32>,
params: vec4<f32>,
}
@group(0) @binding(0) var<storage, read> text_instances: array<TextInstanceData>;
@group(0) @binding(1) var<storage, read> character_colors: array<vec4<f32>>;
@group(1) @binding(0) var font_texture: texture_2d<f32>;
@group(1) @binding(1) var font_sampler: sampler;
struct GlobalUniforms {
projection: mat4x4<f32>,
}
@group(2) @binding(0) var<uniform> globals: GlobalUniforms;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>,
@location(2) character_index: u32,
@location(3) text_instance_index: u32,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
@location(1) screen_pos: vec2<f32>,
@location(2) @interpolate(flat) character_index: u32,
@location(3) @interpolate(flat) text_instance_index: u32,
}
fn median(r: f32, g: f32, b: f32) -> f32 {
return max(min(r, g), min(max(r, g), b));
}
@vertex
fn vs_main(vertex: VertexInput) -> VertexOutput {
var output: VertexOutput;
let inst = text_instances[vertex.text_instance_index];
let depth = inst.params.y;
output.position = globals.projection * vec4<f32>(vertex.position.xy, 0.0, 1.0);
output.position.z = depth;
output.tex_coords = vertex.tex_coords;
output.screen_pos = vertex.position.xy;
output.character_index = vertex.character_index;
output.text_instance_index = vertex.text_instance_index;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let inst = text_instances[input.text_instance_index];
let clip = inst.clip_rect;
if clip.z > clip.x {
let frag_pos = input.screen_pos;
if frag_pos.x < clip.x || frag_pos.x > clip.z ||
frag_pos.y < clip.y || frag_pos.y > clip.w {
discard;
}
}
let msd = textureSample(font_texture, font_sampler, input.tex_coords);
let sd = median(msd.r, msd.g, msd.b);
let px_range = 8.0;
let tex_dims = vec2<f32>(textureDimensions(font_texture));
let unit_range = vec2<f32>(px_range) / tex_dims;
let screen_tex_size = vec2<f32>(1.0) / fwidth(input.tex_coords);
let screen_px_range = max(0.5 * dot(unit_range, screen_tex_size), 1.0);
let screen_px_distance = screen_px_range * (sd - 0.5);
let alpha_base = clamp(screen_px_distance + 0.5, 0.0, 1.0);
let char_color = character_colors[input.character_index];
let use_override = char_color.a > 0.0;
let base_color = select(inst.color, char_color, use_override);
let outline_width = inst.params.x;
if outline_width > 0.0 {
let outline_threshold = 0.5 - outline_width;
let outline_screen_dist = screen_px_range * (sd - outline_threshold);
let alpha_outline = clamp(outline_screen_dist + 0.5, 0.0, 1.0);
let final_color = mix(inst.outline_color, base_color, alpha_base / max(alpha_outline, 0.001));
return vec4<f32>(final_color.rgb, final_color.a * alpha_outline);
} else {
return vec4<f32>(base_color.rgb, base_color.a * alpha_base);
}
}