From afb6fd97db079dffebdf39d0f65fc0c6fd017c66 Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Fri, 1 Jan 2021 22:24:54 -0800 Subject: [PATCH 1/2] cleanup unnecessary changes from PR #1171 --- crates/bevy_text/src/glyph_brush.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index 074e861b5eef0..111c47a7cccd3 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -76,12 +76,10 @@ impl GlyphBrush { let SectionGlyph { section_index: _, byte_index: _, - mut glyph, + glyph, font_id: _, } = sg; let glyph_id = glyph.id; - let base_x = glyph.position.x.floor(); - glyph.position.x = 0.; if let Some(outlined_glyph) = font.font.outline_glyph(glyph) { let bounds = outlined_glyph.px_bounds(); let handle_font_atlas: Handle = handle.as_weak(); @@ -100,9 +98,7 @@ impl GlyphBrush { let glyph_width = glyph_rect.width(); let glyph_height = glyph_rect.height(); - let x = base_x + bounds.min.x + glyph_width / 2.0 - min_x; - // the 0.5 accounts for odd-numbered heights (bump up by 1 pixel) - // max_y = text block height, and up is negative (whereas for transform, up is positive) + let x = bounds.min.x + glyph_width / 2.0 - min_x; let y = max_y - bounds.max.y + glyph_height / 2.0; let position = Vec2::new(x, y); From b275d0b50a6d25181ba2cecbe17aef788836e30b Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Sat, 2 Jan 2021 17:45:00 -0800 Subject: [PATCH 2/2] add feature to correctly render glyphs with sub-pixel positioning --- Cargo.toml | 3 ++ crates/bevy_internal/Cargo.toml | 3 ++ crates/bevy_text/Cargo.toml | 3 ++ crates/bevy_text/src/font_atlas.rs | 54 ++++++++++++++++++++++---- crates/bevy_text/src/font_atlas_set.rs | 25 +++++++++--- crates/bevy_text/src/glyph_brush.rs | 45 +++++++++++++++++++-- docs/cargo_features.md | 6 +++ 7 files changed, 122 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 097ab58410a63..17edc06eb1c20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,9 @@ serialize = ["bevy_internal/serialize"] wayland = ["bevy_internal/wayland"] x11 = ["bevy_internal/x11"] +# enable rendering of font glyphs using subpixel accuracy +subpixel_glyph_atlas = ["bevy_internal/subpixel_glyph_atlas"] + [dependencies] bevy_dylib = {path = "crates/bevy_dylib", version = "0.4.0", default-features = false, optional = true} bevy_internal = {path = "crates/bevy_internal", version = "0.4.0", default-features = false} diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 82baa712cd4c4..d4556746d3508 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -38,6 +38,9 @@ serialize = ["bevy_input/serialize"] wayland = ["bevy_winit/wayland"] x11 = ["bevy_winit/x11"] +# enable rendering of font glyphs using subpixel accuracy +subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"] + [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.4.0" } diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index ba6e22f241c45..855f70398b6db 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -12,6 +12,9 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT" keywords = ["bevy"] +[features] +subpixel_glyph_atlas = [] + [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.4.0" } diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index 0120d70ea3682..bcfa7936a47ab 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -1,13 +1,44 @@ -use ab_glyph::GlyphId; +use ab_glyph::{GlyphId, Point}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlas}; use bevy_utils::HashMap; +#[cfg(feature = "subpixel_glyph_atlas")] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct SubpixelOffset { + x: u16, + y: u16, +} + +#[cfg(feature = "subpixel_glyph_atlas")] +impl From for SubpixelOffset { + fn from(p: Point) -> Self { + fn f(v: f32) -> u16 { + ((v % 1.) * (u16::MAX as f32)) as u16 + } + Self { + x: f(p.x), + y: f(p.y), + } + } +} + +#[cfg(not(feature = "subpixel_glyph_atlas"))] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct SubpixelOffset; + +#[cfg(not(feature = "subpixel_glyph_atlas"))] +impl From for SubpixelOffset { + fn from(_: Point) -> Self { + Self + } +} + pub struct FontAtlas { pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder, - pub glyph_to_atlas_index: HashMap, + pub glyph_to_atlas_index: HashMap<(GlyphId, SubpixelOffset), u32>, pub texture_atlas: Handle, } @@ -31,12 +62,19 @@ impl FontAtlas { } } - pub fn get_glyph_index(&self, glyph_id: GlyphId) -> Option { - self.glyph_to_atlas_index.get(&glyph_id).copied() + pub fn get_glyph_index( + &self, + glyph_id: GlyphId, + subpixel_offset: SubpixelOffset, + ) -> Option { + self.glyph_to_atlas_index + .get(&(glyph_id, subpixel_offset)) + .copied() } - pub fn has_glyph(&self, glyph_id: GlyphId) -> bool { - self.glyph_to_atlas_index.contains_key(&glyph_id) + pub fn has_glyph(&self, glyph_id: GlyphId, subpixel_offset: SubpixelOffset) -> bool { + self.glyph_to_atlas_index + .contains_key(&(glyph_id, subpixel_offset)) } pub fn add_glyph( @@ -44,6 +82,7 @@ impl FontAtlas { textures: &mut Assets, texture_atlases: &mut Assets, glyph_id: GlyphId, + subpixel_offset: SubpixelOffset, texture: &Texture, ) -> bool { let texture_atlas = texture_atlases.get_mut(&self.texture_atlas).unwrap(); @@ -51,7 +90,8 @@ impl FontAtlas { self.dynamic_texture_atlas_builder .add_texture(texture_atlas, textures, texture) { - self.glyph_to_atlas_index.insert(glyph_id, index); + self.glyph_to_atlas_index + .insert((glyph_id, subpixel_offset), index); true } else { false diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index afdc7652c9e9f..3ccca9b5e77ae 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -1,5 +1,5 @@ use crate::{error::TextError, Font, FontAtlas}; -use ab_glyph::{GlyphId, OutlinedGlyph}; +use ab_glyph::{GlyphId, OutlinedGlyph, Point}; use bevy_asset::{Assets, Handle}; use bevy_core::FloatOrd; use bevy_math::Vec2; @@ -35,11 +35,13 @@ impl FontAtlasSet { self.font_atlases.iter() } - pub fn has_glyph(&self, glyph_id: GlyphId, font_size: f32) -> bool { + pub fn has_glyph(&self, glyph_id: GlyphId, glyph_position: Point, font_size: f32) -> bool { self.font_atlases .get(&FloatOrd(font_size)) .map_or(false, |font_atlas| { - font_atlas.iter().any(|atlas| atlas.has_glyph(glyph_id)) + font_atlas + .iter() + .any(|atlas| atlas.has_glyph(glyph_id, glyph_position.into())) }) } @@ -51,6 +53,7 @@ impl FontAtlasSet { ) -> Result { let glyph = outlined_glyph.glyph(); let glyph_id = glyph.id; + let glyph_position = glyph.position; let font_size = glyph.scale.y; let font_atlases = self .font_atlases @@ -64,7 +67,13 @@ impl FontAtlasSet { }); let glyph_texture = Font::get_outlined_glyph_texture(outlined_glyph); let add_char_to_font_atlas = |atlas: &mut FontAtlas| -> bool { - atlas.add_glyph(textures, texture_atlases, glyph_id, &glyph_texture) + atlas.add_glyph( + textures, + texture_atlases, + glyph_id, + glyph_position.into(), + &glyph_texture, + ) }; if !font_atlases.iter_mut().any(add_char_to_font_atlas) { font_atlases.push(FontAtlas::new( @@ -76,19 +85,23 @@ impl FontAtlasSet { textures, texture_atlases, glyph_id, + glyph_position.into(), &glyph_texture, ) { return Err(TextError::FailedToAddGlyph(glyph_id)); } } - Ok(self.get_glyph_atlas_info(font_size, glyph_id).unwrap()) + Ok(self + .get_glyph_atlas_info(font_size, glyph_id, glyph_position) + .unwrap()) } pub fn get_glyph_atlas_info( &self, font_size: f32, glyph_id: GlyphId, + position: Point, ) -> Option { self.font_atlases .get(&FloatOrd(font_size)) @@ -97,7 +110,7 @@ impl FontAtlasSet { .iter() .find_map(|atlas| { atlas - .get_glyph_index(glyph_id) + .get_glyph_index(glyph_id, position.into()) .map(|glyph_index| (glyph_index, atlas.texture_atlas.clone_weak())) }) .map(|(glyph_index, texture_atlas)| GlyphAtlasInfo { diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index 111c47a7cccd3..d225bf9aa5d8d 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -1,4 +1,4 @@ -use ab_glyph::{Font as _, FontArc, ScaleFont as _}; +use ab_glyph::{Font as _, FontArc, Glyph, ScaleFont as _}; use bevy_asset::{Assets, Handle}; use bevy_math::{Size, Vec2}; use bevy_render::prelude::Texture; @@ -76,10 +76,12 @@ impl GlyphBrush { let SectionGlyph { section_index: _, byte_index: _, - glyph, + mut glyph, font_id: _, } = sg; let glyph_id = glyph.id; + let glyph_position = glyph.position; + let adjust = GlyphPlacementAdjuster::new(&mut glyph); if let Some(outlined_glyph) = font.font.outline_glyph(glyph) { let bounds = outlined_glyph.px_bounds(); let handle_font_atlas: Handle = handle.as_weak(); @@ -87,7 +89,7 @@ impl GlyphBrush { .get_or_insert_with(handle_font_atlas, FontAtlasSet::default); let atlas_info = font_atlas_set - .get_glyph_atlas_info(font_size, glyph_id) + .get_glyph_atlas_info(font_size, glyph_id, glyph_position) .map(Ok) .unwrap_or_else(|| { font_atlas_set.add_glyph_to_atlas(texture_atlases, textures, outlined_glyph) @@ -100,7 +102,7 @@ impl GlyphBrush { let x = bounds.min.x + glyph_width / 2.0 - min_x; let y = max_y - bounds.max.y + glyph_height / 2.0; - let position = Vec2::new(x, y); + let position = adjust.position(Vec2::new(x, y)); positioned_glyphs.push(PositionedGlyph { position, @@ -125,3 +127,38 @@ pub struct PositionedGlyph { pub position: Vec2, pub atlas_info: GlyphAtlasInfo, } + +#[cfg(feature = "subpixel_glyph_atlas")] +struct GlyphPlacementAdjuster; + +#[cfg(feature = "subpixel_glyph_atlas")] +impl GlyphPlacementAdjuster { + #[inline(always)] + pub fn new(_: &mut Glyph) -> Self { + Self + } + + #[inline(always)] + pub fn position(&self, p: Vec2) -> Vec2 { + p + } +} + +#[cfg(not(feature = "subpixel_glyph_atlas"))] +struct GlyphPlacementAdjuster(f32); + +#[cfg(not(feature = "subpixel_glyph_atlas"))] +impl GlyphPlacementAdjuster { + #[inline(always)] + pub fn new(glyph: &mut Glyph) -> Self { + let v = glyph.position.x.round(); + glyph.position.x = 0.; + glyph.position.y = glyph.position.y.ceil(); + Self(v) + } + + #[inline(always)] + pub fn position(&self, v: Vec2) -> Vec2 { + Vec2::new(self.0, 0.) + v + } +} diff --git a/docs/cargo_features.md b/docs/cargo_features.md index f00d4e5881232..97c3ebfe37a12 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -71,3 +71,9 @@ Vorbis audio format support. ### wayland Enable this to use Wayland display server protocol other than X11. + +### subpixel_glyph_atlas + +Enable this to cache glyphs using subpixel accuracy. This increases texture +memory usage as each position requires a separate sprite in the glyph atlas, but +provide more accurate character spacing.