use crate::{
CustomGlyph, CustomGlyphFont, DecodedGlyph, EncoderContext, Features, GlyphMaterial,
MaterialEncoder, MeshEncoder, MsdfTextPipeline, RasterizedTextPipeline, Rect,
ScreenSizeUniform, StyledText, TextStyle, TextureAtlas,
};
struct MsdfPass {
encoder: MaterialEncoder,
material_builder: fn(&TextStyle, &DecodedGlyph, Option<Rect>) -> GlyphMaterial,
}
struct MsdfMultiPass {
fill: MsdfPass,
stroke_in: Option<MsdfPass>,
glow_in: Option<MsdfPass>,
stroke_out: Option<MsdfPass>,
glow_out: Option<MsdfPass>,
shadow: Option<MsdfPass>,
}
enum MsdfPasses {
Single(MsdfPass),
Multi(Box<MsdfMultiPass>),
}
pub struct TextRenderer {
#[allow(dead_code)]
enabled_features: Features,
msdf_generator: klyff_msdf::MsdfGenerator,
mesh_encoder: MeshEncoder,
msdf_passes: Box<MsdfPasses>,
msdf_pipeline: MsdfTextPipeline,
rasterized_pipeline: RasterizedTextPipeline,
screen_size_uniform: ScreenSizeUniform,
}
impl TextRenderer {
pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
let rasterized_pipeline = RasterizedTextPipeline::new(device, surface_format);
let msdf_pipeline = MsdfTextPipeline::new(device, surface_format);
Self::with_custom_pipeline(device, msdf_pipeline, rasterized_pipeline)
}
pub fn with_custom_pipeline(
device: &wgpu::Device,
msdf_pipeline: MsdfTextPipeline,
rasterized_pipeline: RasterizedTextPipeline,
) -> Self {
let mesh_encoder = MeshEncoder::new(device);
Self {
enabled_features: Features::empty(),
msdf_generator: klyff_msdf::MsdfGenerator::new(),
mesh_encoder,
msdf_passes: Box::new(MsdfPasses::Single(MsdfPass {
encoder: MaterialEncoder::new(device),
material_builder: material_fill,
})),
msdf_pipeline,
rasterized_pipeline,
screen_size_uniform: ScreenSizeUniform::new(device),
}
}
pub fn with_styling(
features: Features,
device: &wgpu::Device,
surface_format: wgpu::TextureFormat,
) -> Self {
let mesh_encoder = MeshEncoder::new(device);
let rasterized_pipeline = RasterizedTextPipeline::new(device, surface_format);
let msdf_pipeline = MsdfTextPipeline::new(device, surface_format);
let make_pass =
|builder: fn(&TextStyle, &DecodedGlyph, Option<Rect>) -> GlyphMaterial| -> MsdfPass {
MsdfPass {
encoder: MaterialEncoder::new(device),
material_builder: builder,
}
};
let multi = MsdfPasses::Multi(Box::new(MsdfMultiPass {
fill: make_pass(material_fill),
stroke_in: features
.contains(Features::STROKE_IN)
.then(|| make_pass(material_stroke_in)),
glow_in: features
.contains(Features::GLOW_IN)
.then(|| make_pass(material_glow_in)),
stroke_out: features
.contains(Features::STROKE_OUT)
.then(|| make_pass(material_stroke_out)),
glow_out: features
.contains(Features::GLOW_OUT)
.then(|| make_pass(material_glow_out)),
shadow: features
.contains(Features::SHADOW)
.then(|| make_pass(material_shadow)),
}));
Self {
enabled_features: features,
msdf_generator: klyff_msdf::MsdfGenerator::new(),
mesh_encoder,
msdf_passes: Box::new(multi),
msdf_pipeline,
rasterized_pipeline,
screen_size_uniform: ScreenSizeUniform::new(device),
}
}
pub fn msdf_generator(&mut self) -> &mut klyff_msdf::MsdfGenerator {
&mut self.msdf_generator
}
pub fn prepare(
&mut self,
ctx: EncoderContext<'_>,
surface_size: (u32, u32),
styled_texts: &[StyledText],
) {
self.prepare_with_glyph_transform(ctx, surface_size, styled_texts, |_, _| {}, |_, _| {});
}
pub fn prepare_with_glyph_transform(
&mut self,
context: EncoderContext<'_>,
surface_size: (u32, u32),
styled_texts: &[StyledText],
per_glyph_transform: impl FnMut(&DecodedGlyph, &mut [glam::Vec2; 4]),
mut per_glyph_styling: impl FnMut(&DecodedGlyph, &mut TextStyle),
) {
profiling::scope!("TextRenderer::prepare_with_glyph_transform");
let ctx = EncoderContext {
atlas: &mut *context.atlas,
device: context.device,
queue: context.queue,
cmd_encoder: context.cmd_encoder,
font_system: context.font_system,
};
self.mesh_encoder.encode(
ctx,
&mut self.msdf_generator,
styled_texts
.iter()
.enumerate()
.map(|(i, st)| st.as_text(i as u64)),
per_glyph_transform,
);
self.screen_size_uniform
.encode(context.queue, surface_size.0, surface_size.1);
let fallback_style = TextStyle::default();
let mesh_encoder = &self.mesh_encoder;
Self::passes_mut(&mut self.msdf_passes, |pass| {
profiling::scope!("TextRenderer::msdf_pass");
let ctx = EncoderContext {
atlas: &mut *context.atlas,
device: context.device,
queue: context.queue,
cmd_encoder: context.cmd_encoder,
font_system: context.font_system,
};
let builder = pass.material_builder;
pass.encoder.encode(ctx, mesh_encoder, |glyph| {
let st = styled_texts.get(glyph.text_id as usize);
let mut style = st
.map(|st| *st.style_at(glyph.metadata))
.unwrap_or(fallback_style);
per_glyph_styling(glyph, &mut style);
let style = style.clamped_to_msdf_range(glyph.font_size_px);
let gradient_region = st.and_then(|st| st.gradient_region);
builder(&style, glyph, gradient_region)
});
});
}
pub fn set_custom_glyph_font(&mut self, font: Option<CustomGlyphFont>) {
self.mesh_encoder.set_custom_glyph_font(font);
}
pub fn custom_glyphs(&self) -> &[CustomGlyph] {
self.mesh_encoder.custom_glyphs()
}
pub fn render<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, atlas: &'a TextureAtlas) {
profiling::scope!("TextRenderer::render");
self.rasterized_pipeline
.render(pass, atlas, &self.screen_size_uniform, &self.mesh_encoder);
match self.msdf_passes.as_ref() {
MsdfPasses::Single(p) => {
self.msdf_pipeline.render(
pass,
atlas,
&self.screen_size_uniform,
&self.mesh_encoder,
&p.encoder,
);
}
MsdfPasses::Multi(multi_pass) => {
let MsdfMultiPass {
fill,
stroke_in,
glow_in,
stroke_out,
glow_out,
shadow,
} = multi_pass.as_ref();
for p in [
shadow.as_ref(),
glow_out.as_ref(),
stroke_out.as_ref(),
Some(fill),
glow_in.as_ref(),
stroke_in.as_ref(),
]
.into_iter()
.flatten()
{
self.msdf_pipeline.render(
pass,
atlas,
&self.screen_size_uniform,
&self.mesh_encoder,
&p.encoder,
);
}
}
}
}
fn passes_mut(p: &mut MsdfPasses, mut f: impl FnMut(&mut MsdfPass)) {
match p {
MsdfPasses::Single(pass) => f(pass),
MsdfPasses::Multi(multi_pass) => {
let MsdfMultiPass {
fill,
stroke_in,
glow_in,
stroke_out,
glow_out,
shadow,
} = multi_pass.as_mut();
f(fill);
if let Some(p) = stroke_in {
f(p);
}
if let Some(p) = glow_in {
f(p);
}
if let Some(p) = stroke_out {
f(p);
}
if let Some(p) = glow_out {
f(p);
}
if let Some(p) = shadow {
f(p);
}
}
}
}
}
const AA: f32 = 1.0;
const LARGE_DIST: f32 = 10000.0;
fn material_fill(
style: &TextStyle,
_glyph: &DecodedGlyph,
gradient_region: Option<Rect>,
) -> GlyphMaterial {
GlyphMaterial {
render_dist_range: (-AA, LARGE_DIST),
max_alpha_dist_range: (0.0, LARGE_DIST),
color: style.color,
offset: glam::Vec2::ZERO,
gradient_region,
roundness: 0.0,
}
}
fn aa_max_alpha_range(from: f32, to: f32, aa: f32) -> (f32, f32) {
if to - from > aa * 2.0 {
(from + aa, to - aa)
} else {
(from + (to - from) * 0.25, from + (to - from) * 0.75)
}
}
fn material_stroke_in(
style: &TextStyle,
_glyph: &DecodedGlyph,
gradient_region: Option<Rect>,
) -> GlyphMaterial {
let w = style.stroke_in.width;
GlyphMaterial {
render_dist_range: (-AA, w - AA),
max_alpha_dist_range: aa_max_alpha_range(-AA, w - AA, AA),
color: style.stroke_in.color,
offset: glam::Vec2::ZERO,
gradient_region,
roundness: style.stroke_in.roundness,
}
}
fn material_stroke_out(
style: &TextStyle,
_glyph: &DecodedGlyph,
gradient_region: Option<Rect>,
) -> GlyphMaterial {
let w = style.stroke_out.width;
GlyphMaterial {
render_dist_range: (-w, AA),
max_alpha_dist_range: aa_max_alpha_range(-w, AA, AA),
color: style.stroke_out.color,
offset: glam::Vec2::ZERO,
gradient_region,
roundness: style.stroke_out.roundness,
}
}
fn material_glow_in(
style: &TextStyle,
_glyph: &DecodedGlyph,
gradient_region: Option<Rect>,
) -> GlyphMaterial {
let s = style.glow_in.width;
let p = style.glow_in.spread;
let shift = style.stroke_in.width;
GlyphMaterial {
render_dist_range: (-AA + shift - AA, s + shift - AA),
max_alpha_dist_range: (shift, (s * p).min((s - AA).max(0.0)) + shift),
color: style.glow_in.color,
offset: glam::Vec2::ZERO,
gradient_region,
roundness: style.glow_in.roundness,
}
}
fn material_glow_out(
style: &TextStyle,
_glyph: &DecodedGlyph,
gradient_region: Option<Rect>,
) -> GlyphMaterial {
let s = style.glow_out.width;
let p = style.glow_out.spread;
let shift = -style.stroke_out.width;
GlyphMaterial {
render_dist_range: (-s + shift + AA, shift + AA),
max_alpha_dist_range: ((-s * p).max((-s + AA).min(0.0)) + shift, shift),
color: style.glow_out.color,
offset: glam::Vec2::ZERO,
gradient_region,
roundness: style.glow_out.roundness,
}
}
fn material_shadow(
style: &TextStyle,
_glyph: &DecodedGlyph,
gradient_region: Option<Rect>,
) -> GlyphMaterial {
let s = style.shadow.additional_width;
let p = style.shadow.spread;
GlyphMaterial {
render_dist_range: (-s, LARGE_DIST),
max_alpha_dist_range: (-s * p + AA, LARGE_DIST),
color: style.shadow.color,
offset: style.shadow.direction,
gradient_region,
roundness: style.shadow.roundness,
}
}