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,
}
@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 alpha = textureSample(font_texture, font_sampler, input.tex_coords).a;
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);
return vec4<f32>(base_color.rgb, base_color.a * alpha);
}
@fragment
fn fs_main_subpixel(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 coverage = textureSample(font_texture, font_sampler, input.tex_coords).rgb;
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 size_ratio = inst.params.z;
let ratio_deviation = abs(size_ratio - 1.0);
let subpixel_strength = clamp(1.0 - ratio_deviation * 3.33, 0.0, 1.0);
let gamma = vec3<f32>(1.0 / 1.4);
let corrected = pow(coverage, gamma);
let avg = (corrected.r + corrected.g + corrected.b) / 3.0;
let blended = mix(vec3<f32>(avg), corrected, subpixel_strength);
let a = base_color.a;
return vec4<f32>(base_color.rgb * blended * a, max(blended.r, max(blended.g, blended.b)) * a);
}