Make the font atlas use a color image (#7298)
* [x] I have followed the instructions in the PR template Splitting this out from the Parley work as requested. This removes `FontImage` and makes the font atlas use a `ColorImage`. It converts alpha to coverage at glyph-drawing time, not at delta-upload time. This doesn't do much now, but will allow for color emoji rendering once we start using Parley. I've changed things around so that we pass in `text_alpha_to_coverage` to the `Fonts` the same way we do with `pixels_per_point` and `max_texture_side`, reusing the existing code to check if the setting differs and recreating the font atlas if so. I'm not quite sure why this wasn't done in the first place. I've left `ImageData` as an enum for now, in case we want to add support for more texture pixel formats in the future (which I personally think would be worthwhile). If you'd like, I can just remove that enum entirely.
This commit is contained in:
parent
d94386de3d
commit
7ac137bfc1
|
|
@ -564,19 +564,6 @@ impl Renderer {
|
|||
);
|
||||
Cow::Borrowed(&image.pixels)
|
||||
}
|
||||
epaint::ImageData::Font(image) => {
|
||||
assert_eq!(
|
||||
width as usize * height as usize,
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
profiling::scope!("font -> sRGBA");
|
||||
Cow::Owned(
|
||||
image
|
||||
.srgba_pixels(Default::default())
|
||||
.collect::<Vec<epaint::Color32>>(),
|
||||
)
|
||||
}
|
||||
};
|
||||
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl Default for WrappedTextureManager {
|
|||
// Will be filled in later
|
||||
let font_id = tex_mngr.alloc(
|
||||
"egui_font_texture".into(),
|
||||
epaint::FontImage::new([0, 0]).into(),
|
||||
epaint::ColorImage::filled([0, 0], Color32::TRANSPARENT).into(),
|
||||
Default::default(),
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -610,6 +610,8 @@ impl ContextImpl {
|
|||
log::trace!("Adding new fonts");
|
||||
}
|
||||
|
||||
let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage;
|
||||
|
||||
let mut is_new = false;
|
||||
|
||||
let fonts = self
|
||||
|
|
@ -624,13 +626,14 @@ impl ContextImpl {
|
|||
Fonts::new(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
text_alpha_from_coverage,
|
||||
self.font_definitions.clone(),
|
||||
)
|
||||
});
|
||||
|
||||
{
|
||||
profiling::scope!("Fonts::begin_pass");
|
||||
fonts.begin_pass(pixels_per_point, max_texture_side);
|
||||
fonts.begin_pass(pixels_per_point, max_texture_side, text_alpha_from_coverage);
|
||||
}
|
||||
|
||||
if is_new && self.memory.options.preload_font_glyphs {
|
||||
|
|
@ -1921,16 +1924,6 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reset_font_atlas(&self) {
|
||||
let pixels_per_point = self.pixels_per_point();
|
||||
let fonts = self.read(|ctx| {
|
||||
ctx.fonts
|
||||
.get(&pixels_per_point.into())
|
||||
.map(|current_fonts| current_fonts.lock().fonts.definitions().clone())
|
||||
});
|
||||
self.memory_mut(|mem| mem.new_font_definitions = fonts);
|
||||
}
|
||||
|
||||
/// Tell `egui` which fonts to use.
|
||||
///
|
||||
/// The default `egui` fonts only support latin and cyrillic alphabets,
|
||||
|
|
@ -2066,19 +2059,10 @@ impl Context {
|
|||
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
|
||||
pub fn set_style_of(&self, theme: Theme, style: impl Into<Arc<Style>>) {
|
||||
let style = style.into();
|
||||
let mut recreate_font_atlas = false;
|
||||
self.options_mut(|opt| {
|
||||
let dest = match theme {
|
||||
Theme::Dark => &mut opt.dark_style,
|
||||
Theme::Light => &mut opt.light_style,
|
||||
};
|
||||
recreate_font_atlas =
|
||||
dest.visuals.text_alpha_from_coverage != style.visuals.text_alpha_from_coverage;
|
||||
*dest = style;
|
||||
self.options_mut(|opt| match theme {
|
||||
Theme::Dark => opt.dark_style = style,
|
||||
Theme::Light => opt.light_style = style,
|
||||
});
|
||||
if recreate_font_atlas {
|
||||
self.reset_font_atlas();
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`crate::Visuals`] used by all subsequent windows, panels etc.
|
||||
|
|
@ -2475,28 +2459,7 @@ impl ContextImpl {
|
|||
}
|
||||
|
||||
// Inform the backend of all textures that have been updated (including font atlas).
|
||||
let textures_delta = {
|
||||
// HACK to get much nicer looking text in light mode.
|
||||
// This assumes all text is black-on-white in light mode,
|
||||
// and white-on-black in dark mode, which is not necessarily true,
|
||||
// but often close enough.
|
||||
// Of course this fails for cases when there is black-on-white text in dark mode,
|
||||
// and white-on-black text in light mode.
|
||||
|
||||
let text_alpha_from_coverage =
|
||||
self.memory.options.style().visuals.text_alpha_from_coverage;
|
||||
|
||||
let mut textures_delta = self.tex_manager.0.write().take_delta();
|
||||
|
||||
for (_, delta) in &mut textures_delta.set {
|
||||
if let ImageData::Font(font) = &mut delta.image {
|
||||
delta.image =
|
||||
ImageData::Color(font.to_color_image(text_alpha_from_coverage).into());
|
||||
}
|
||||
}
|
||||
|
||||
textures_delta
|
||||
};
|
||||
let textures_delta = self.tex_manager.0.write().take_delta();
|
||||
|
||||
let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output);
|
||||
|
||||
|
|
@ -3094,17 +3057,9 @@ impl Context {
|
|||
|
||||
options.ui(ui);
|
||||
|
||||
let text_alpha_from_coverage_changed =
|
||||
prev_options.style().visuals.text_alpha_from_coverage
|
||||
!= options.style().visuals.text_alpha_from_coverage;
|
||||
|
||||
if options != prev_options {
|
||||
self.options_mut(move |o| *o = options);
|
||||
}
|
||||
|
||||
if text_alpha_from_coverage_changed {
|
||||
ui.ctx().reset_font_atlas();
|
||||
}
|
||||
}
|
||||
|
||||
fn fonts_tweak_ui(&self, ui: &mut Ui) {
|
||||
|
|
|
|||
|
|
@ -467,7 +467,7 @@ pub use emath::{
|
|||
remap_clamp, vec2,
|
||||
};
|
||||
pub use epaint::{
|
||||
ClippedPrimitive, ColorImage, CornerRadius, FontImage, ImageData, Margin, Mesh, PaintCallback,
|
||||
ClippedPrimitive, ColorImage, CornerRadius, ImageData, Margin, Mesh, PaintCallback,
|
||||
PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
|
||||
textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta},
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
let fonts = egui::epaint::text::Fonts::new(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
egui::epaint::AlphaFromCoverage::default(),
|
||||
egui::FontDefinitions::default(),
|
||||
);
|
||||
{
|
||||
|
|
@ -210,7 +211,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
|
||||
let mut rng = rand::rng();
|
||||
b.iter(|| {
|
||||
fonts.begin_pass(pixels_per_point, max_texture_side);
|
||||
fonts.begin_pass(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
egui::epaint::AlphaFromCoverage::default(),
|
||||
);
|
||||
|
||||
// Delete a random character, simulating a user making an edit in a long file:
|
||||
let mut new_string = string.clone();
|
||||
|
|
|
|||
|
|
@ -534,23 +534,6 @@ impl Painter {
|
|||
|
||||
self.upload_texture_srgb(delta.pos, image.size, delta.options, data);
|
||||
}
|
||||
egui::ImageData::Font(image) => {
|
||||
assert_eq!(
|
||||
image.width() * image.height(),
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
let data: Vec<u8> = {
|
||||
profiling::scope!("font -> sRGBA");
|
||||
image
|
||||
.srgba_pixels(Default::default())
|
||||
.flat_map(|a| a.to_array())
|
||||
.collect()
|
||||
};
|
||||
|
||||
self.upload_texture_srgb(delta.pos, image.size, delta.options, &data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
||||
|
||||
use epaint::{
|
||||
ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke, TessellationOptions,
|
||||
Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path,
|
||||
AlphaFromCoverage, ClippedShape, Color32, Mesh, PathStroke, Pos2, Rect, Shape, Stroke,
|
||||
TessellationOptions, Tessellator, TextureAtlas, Vec2, pos2, tessellator::Path,
|
||||
};
|
||||
|
||||
#[global_allocator]
|
||||
|
|
@ -66,7 +66,7 @@ fn tessellate_circles(c: &mut Criterion) {
|
|||
let pixels_per_point = 2.0;
|
||||
let options = TessellationOptions::default();
|
||||
|
||||
let atlas = TextureAtlas::new([4096, 256]);
|
||||
let atlas = TextureAtlas::new([4096, 256], AlphaFromCoverage::default());
|
||||
let font_tex_size = atlas.size();
|
||||
let prepared_discs = atlas.prepared_discs();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,24 +7,20 @@ use std::sync::Arc;
|
|||
///
|
||||
/// To load an image file, see [`ColorImage::from_rgba_unmultiplied`].
|
||||
///
|
||||
/// In order to paint the image on screen, you first need to convert it to
|
||||
/// This is currently an enum with only one variant, but more image types may be added in the future.
|
||||
///
|
||||
/// See also: [`ColorImage`], [`FontImage`].
|
||||
#[derive(Clone, PartialEq)]
|
||||
/// See also: [`ColorImage`].
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum ImageData {
|
||||
/// RGBA image.
|
||||
Color(Arc<ColorImage>),
|
||||
|
||||
/// Used for the font texture.
|
||||
Font(FontImage),
|
||||
}
|
||||
|
||||
impl ImageData {
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
match self {
|
||||
Self::Color(image) => image.size,
|
||||
Self::Font(image) => image.size,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +34,7 @@ impl ImageData {
|
|||
|
||||
pub fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
Self::Color(_) | Self::Font(_) => 4,
|
||||
Self::Color(_) => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -271,6 +267,37 @@ impl ColorImage {
|
|||
}
|
||||
Self::new([width, height], output)
|
||||
}
|
||||
|
||||
/// Clone a sub-region as a new image.
|
||||
pub fn region_by_pixels(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self {
|
||||
assert!(
|
||||
x + w <= self.width(),
|
||||
"x + w should be <= self.width(), but x: {}, w: {}, width: {}",
|
||||
x,
|
||||
w,
|
||||
self.width()
|
||||
);
|
||||
assert!(
|
||||
y + h <= self.height(),
|
||||
"y + h should be <= self.height(), but y: {}, h: {}, height: {}",
|
||||
y,
|
||||
h,
|
||||
self.height()
|
||||
);
|
||||
|
||||
let mut pixels = Vec::with_capacity(w * h);
|
||||
for y in y..y + h {
|
||||
let offset = y * self.width() + x;
|
||||
pixels.extend(&self.pixels[offset..(offset + w)]);
|
||||
}
|
||||
assert_eq!(
|
||||
pixels.len(),
|
||||
w * h,
|
||||
"pixels.len should be w * h, but got {}",
|
||||
pixels.len()
|
||||
);
|
||||
Self::new([w, h], pixels)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for ColorImage {
|
||||
|
|
@ -371,127 +398,12 @@ impl AlphaFromCoverage {
|
|||
}
|
||||
}
|
||||
|
||||
/// A single-channel image designed for the font texture.
|
||||
///
|
||||
/// Each value represents "coverage", i.e. how much a texel is covered by a character.
|
||||
///
|
||||
/// This is roughly interpreted as the opacity of a white image.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct FontImage {
|
||||
/// width, height
|
||||
pub size: [usize; 2],
|
||||
|
||||
/// The coverage value.
|
||||
///
|
||||
/// Often you want to use [`Self::srgba_pixels`] instead.
|
||||
pub pixels: Vec<f32>,
|
||||
}
|
||||
|
||||
impl FontImage {
|
||||
pub fn new(size: [usize; 2]) -> Self {
|
||||
Self {
|
||||
size,
|
||||
pixels: vec![0.0; size[0] * size[1]],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> usize {
|
||||
self.size[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize {
|
||||
self.size[1]
|
||||
}
|
||||
|
||||
/// Returns the textures as `sRGBA` premultiplied pixels, row by row, top to bottom.
|
||||
#[inline]
|
||||
pub fn srgba_pixels(
|
||||
&self,
|
||||
alpha_from_coverage: AlphaFromCoverage,
|
||||
) -> impl ExactSizeIterator<Item = Color32> + '_ {
|
||||
self.pixels
|
||||
.iter()
|
||||
.map(move |&coverage| alpha_from_coverage.color_from_coverage(coverage))
|
||||
}
|
||||
|
||||
/// Convert this coverage image to a [`ColorImage`].
|
||||
pub fn to_color_image(&self, alpha_from_coverage: AlphaFromCoverage) -> ColorImage {
|
||||
profiling::function_scope!();
|
||||
let pixels = self.srgba_pixels(alpha_from_coverage).collect();
|
||||
ColorImage::new(self.size, pixels)
|
||||
}
|
||||
|
||||
/// Clone a sub-region as a new image.
|
||||
pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> Self {
|
||||
assert!(
|
||||
x + w <= self.width(),
|
||||
"x + w should be <= self.width(), but x: {}, w: {}, width: {}",
|
||||
x,
|
||||
w,
|
||||
self.width()
|
||||
);
|
||||
assert!(
|
||||
y + h <= self.height(),
|
||||
"y + h should be <= self.height(), but y: {}, h: {}, height: {}",
|
||||
y,
|
||||
h,
|
||||
self.height()
|
||||
);
|
||||
|
||||
let mut pixels = Vec::with_capacity(w * h);
|
||||
for y in y..y + h {
|
||||
let offset = y * self.width() + x;
|
||||
pixels.extend(&self.pixels[offset..(offset + w)]);
|
||||
}
|
||||
assert_eq!(
|
||||
pixels.len(),
|
||||
w * h,
|
||||
"pixels.len should be w * h, but got {}",
|
||||
pixels.len()
|
||||
);
|
||||
Self {
|
||||
size: [w, h],
|
||||
pixels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for FontImage {
|
||||
type Output = f32;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, (x, y): (usize, usize)) -> &f32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
|
||||
&self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for FontImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 {
|
||||
let [w, h] = self.size;
|
||||
assert!(x < w && y < h, "x: {x}, y: {y}, w: {w}, h: {h}");
|
||||
&mut self.pixels[y * w + x]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontImage> for ImageData {
|
||||
#[inline(always)]
|
||||
fn from(image: FontImage) -> Self {
|
||||
Self::Font(image)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A change to an image.
|
||||
///
|
||||
/// Either a whole new image, or an update to a rectangular region of it.
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[must_use = "The painter must take care of this"]
|
||||
pub struct ImageDelta {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ pub use self::{
|
|||
color::ColorMode,
|
||||
corner_radius::CornerRadius,
|
||||
corner_radius_f32::CornerRadiusF32,
|
||||
image::{AlphaFromCoverage, ColorImage, FontImage, ImageData, ImageDelta},
|
||||
image::{AlphaFromCoverage, ColorImage, ImageData, ImageDelta},
|
||||
margin::Margin,
|
||||
margin_f32::*,
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
|
|
|
|||
|
|
@ -179,7 +179,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn text_bounding_box_under_rotation() {
|
||||
let fonts = Fonts::new(1.0, 1024, FontDefinitions::default());
|
||||
let fonts = Fonts::new(
|
||||
1.0,
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let font = FontId::monospace(12.0);
|
||||
|
||||
let mut t = crate::Shape::text(
|
||||
|
|
|
|||
|
|
@ -279,12 +279,13 @@ impl FontImpl {
|
|||
} else {
|
||||
let glyph_pos = {
|
||||
let atlas = &mut self.atlas.lock();
|
||||
let text_alpha_from_coverage = atlas.text_alpha_from_coverage;
|
||||
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
|
||||
glyph.draw(|x, y, v| {
|
||||
if 0.0 < v {
|
||||
let px = glyph_pos.0 + x as usize;
|
||||
let py = glyph_pos.1 + y as usize;
|
||||
image[(px, py)] = v;
|
||||
image[(px, py)] = text_alpha_from_coverage.color_from_coverage(v);
|
||||
}
|
||||
});
|
||||
glyph_pos
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
TextureAtlas,
|
||||
AlphaFromCoverage, TextureAtlas,
|
||||
mutex::{Mutex, MutexGuard},
|
||||
text::{
|
||||
Galley, LayoutJob, LayoutSection,
|
||||
|
|
@ -430,36 +430,56 @@ impl Fonts {
|
|||
pub fn new(
|
||||
pixels_per_point: f32,
|
||||
max_texture_side: usize,
|
||||
text_alpha_from_coverage: AlphaFromCoverage,
|
||||
definitions: FontDefinitions,
|
||||
) -> Self {
|
||||
let fonts_and_cache = FontsAndCache {
|
||||
fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions),
|
||||
fonts: FontsImpl::new(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
text_alpha_from_coverage,
|
||||
definitions,
|
||||
),
|
||||
galley_cache: Default::default(),
|
||||
};
|
||||
Self(Arc::new(Mutex::new(fonts_and_cache)))
|
||||
}
|
||||
|
||||
/// Call at the start of each frame with the latest known
|
||||
/// `pixels_per_point` and `max_texture_side`.
|
||||
/// `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`.
|
||||
///
|
||||
/// Call after painting the previous frame, but before using [`Fonts`] for the new frame.
|
||||
///
|
||||
/// This function will react to changes in `pixels_per_point` and `max_texture_side`,
|
||||
/// This function will react to changes in `pixels_per_point`, `max_texture_side`, and `text_alpha_from_coverage`,
|
||||
/// as well as notice when the font atlas is getting full, and handle that.
|
||||
pub fn begin_pass(&self, pixels_per_point: f32, max_texture_side: usize) {
|
||||
pub fn begin_pass(
|
||||
&self,
|
||||
pixels_per_point: f32,
|
||||
max_texture_side: usize,
|
||||
text_alpha_from_coverage: AlphaFromCoverage,
|
||||
) {
|
||||
let mut fonts_and_cache = self.0.lock();
|
||||
|
||||
let pixels_per_point_changed = fonts_and_cache.fonts.pixels_per_point != pixels_per_point;
|
||||
let max_texture_side_changed = fonts_and_cache.fonts.max_texture_side != max_texture_side;
|
||||
let text_alpha_from_coverage_changed =
|
||||
fonts_and_cache.fonts.atlas.lock().text_alpha_from_coverage != text_alpha_from_coverage;
|
||||
let font_atlas_almost_full = fonts_and_cache.fonts.atlas.lock().fill_ratio() > 0.8;
|
||||
let needs_recreate =
|
||||
pixels_per_point_changed || max_texture_side_changed || font_atlas_almost_full;
|
||||
let needs_recreate = pixels_per_point_changed
|
||||
|| max_texture_side_changed
|
||||
|| text_alpha_from_coverage_changed
|
||||
|| font_atlas_almost_full;
|
||||
|
||||
if needs_recreate {
|
||||
let definitions = fonts_and_cache.fonts.definitions.clone();
|
||||
|
||||
*fonts_and_cache = FontsAndCache {
|
||||
fonts: FontsImpl::new(pixels_per_point, max_texture_side, definitions),
|
||||
fonts: FontsImpl::new(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
text_alpha_from_coverage,
|
||||
definitions,
|
||||
),
|
||||
galley_cache: Default::default(),
|
||||
};
|
||||
}
|
||||
|
|
@ -497,7 +517,7 @@ impl Fonts {
|
|||
|
||||
/// The full font atlas image.
|
||||
#[inline]
|
||||
pub fn image(&self) -> crate::FontImage {
|
||||
pub fn image(&self) -> crate::ColorImage {
|
||||
self.lock().fonts.atlas.lock().image().clone()
|
||||
}
|
||||
|
||||
|
|
@ -642,6 +662,7 @@ impl FontsImpl {
|
|||
pub fn new(
|
||||
pixels_per_point: f32,
|
||||
max_texture_side: usize,
|
||||
text_alpha_from_coverage: AlphaFromCoverage,
|
||||
definitions: FontDefinitions,
|
||||
) -> Self {
|
||||
assert!(
|
||||
|
|
@ -651,7 +672,7 @@ impl FontsImpl {
|
|||
|
||||
let texture_width = max_texture_side.at_most(16 * 1024);
|
||||
let initial_height = 32; // Keep initial font atlas small, so it is fast to upload to GPU. This will expand as needed anyways.
|
||||
let atlas = TextureAtlas::new([texture_width, initial_height]);
|
||||
let atlas = TextureAtlas::new([texture_width, initial_height], text_alpha_from_coverage);
|
||||
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
|
||||
|
|
@ -1120,6 +1141,7 @@ mod tests {
|
|||
let mut fonts = FontsImpl::new(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1034,11 +1034,18 @@ fn is_cjk_break_allowed(c: char) -> bool {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::AlphaFromCoverage;
|
||||
|
||||
use super::{super::*, *};
|
||||
|
||||
#[test]
|
||||
fn test_zero_max_width() {
|
||||
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
|
||||
let mut fonts = FontsImpl::new(
|
||||
1.0,
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let mut layout_job = LayoutJob::single_section("W".into(), TextFormat::default());
|
||||
layout_job.wrap.max_width = 0.0;
|
||||
let galley = layout(&mut fonts, layout_job.into());
|
||||
|
|
@ -1049,7 +1056,12 @@ mod tests {
|
|||
fn test_truncate_with_newline() {
|
||||
// No matter where we wrap, we should be appending the newline character.
|
||||
|
||||
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
|
||||
let mut fonts = FontsImpl::new(
|
||||
1.0,
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let text_format = TextFormat {
|
||||
font_id: FontId::monospace(12.0),
|
||||
..Default::default()
|
||||
|
|
@ -1094,7 +1106,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_cjk() {
|
||||
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
|
||||
let mut fonts = FontsImpl::new(
|
||||
1.0,
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let mut layout_job = LayoutJob::single_section(
|
||||
"日本語とEnglishの混在した文章".into(),
|
||||
TextFormat::default(),
|
||||
|
|
@ -1109,7 +1126,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_pre_cjk() {
|
||||
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
|
||||
let mut fonts = FontsImpl::new(
|
||||
1.0,
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let mut layout_job = LayoutJob::single_section(
|
||||
"日本語とEnglishの混在した文章".into(),
|
||||
TextFormat::default(),
|
||||
|
|
@ -1124,7 +1146,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_truncate_width() {
|
||||
let mut fonts = FontsImpl::new(1.0, 1024, FontDefinitions::default());
|
||||
let mut fonts = FontsImpl::new(
|
||||
1.0,
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
let mut layout_job =
|
||||
LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default());
|
||||
layout_job.wrap.max_width = f32::INFINITY;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use ecolor::Color32;
|
||||
use emath::{Rect, remap_clamp};
|
||||
|
||||
use crate::{FontImage, ImageDelta};
|
||||
use crate::{AlphaFromCoverage, ColorImage, ImageDelta};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
struct Rectu {
|
||||
|
|
@ -57,7 +58,7 @@ pub struct PreparedDisc {
|
|||
/// More characters can be added, possibly expanding the texture.
|
||||
#[derive(Clone)]
|
||||
pub struct TextureAtlas {
|
||||
image: FontImage,
|
||||
image: ColorImage,
|
||||
|
||||
/// What part of the image that is dirty
|
||||
dirty: Rectu,
|
||||
|
|
@ -72,18 +73,22 @@ pub struct TextureAtlas {
|
|||
|
||||
/// pre-rasterized discs of radii `2^i`, where `i` is the index.
|
||||
discs: Vec<PrerasterizedDisc>,
|
||||
|
||||
/// Controls how to convert glyph coverage to alpha.
|
||||
pub(crate) text_alpha_from_coverage: AlphaFromCoverage,
|
||||
}
|
||||
|
||||
impl TextureAtlas {
|
||||
pub fn new(size: [usize; 2]) -> Self {
|
||||
pub fn new(size: [usize; 2], text_alpha_from_coverage: AlphaFromCoverage) -> Self {
|
||||
assert!(size[0] >= 1024, "Tiny texture atlas");
|
||||
let mut atlas = Self {
|
||||
image: FontImage::new(size),
|
||||
image: ColorImage::filled(size, Color32::TRANSPARENT),
|
||||
dirty: Rectu::EVERYTHING,
|
||||
cursor: (0, 0),
|
||||
row_height: 0,
|
||||
overflowed: false,
|
||||
discs: vec![], // will be filled in below
|
||||
text_alpha_from_coverage,
|
||||
};
|
||||
|
||||
// Make the top left pixel fully white for `WHITE_UV`, i.e. painting something with solid color:
|
||||
|
|
@ -93,7 +98,7 @@ impl TextureAtlas {
|
|||
(0, 0),
|
||||
"Expected the first allocation to be at (0, 0), but was at {pos:?}"
|
||||
);
|
||||
image[pos] = 1.0;
|
||||
image[pos] = Color32::WHITE;
|
||||
|
||||
// Allocate a series of anti-aliased discs used to render small filled circles:
|
||||
// TODO(emilk): these circles can be packed A LOT better.
|
||||
|
|
@ -116,7 +121,7 @@ impl TextureAtlas {
|
|||
let coverage =
|
||||
remap_clamp(distance_to_center, (r - 0.5)..=(r + 0.5), 1.0..=0.0);
|
||||
image[((x as i32 + hw + dx) as usize, (y as i32 + hw + dy) as usize)] =
|
||||
coverage;
|
||||
text_alpha_from_coverage.color_from_coverage(coverage);
|
||||
}
|
||||
}
|
||||
atlas.discs.push(PrerasterizedDisc {
|
||||
|
|
@ -184,7 +189,7 @@ impl TextureAtlas {
|
|||
|
||||
/// The full font atlas image.
|
||||
#[inline]
|
||||
pub fn image(&self) -> &FontImage {
|
||||
pub fn image(&self) -> &ColorImage {
|
||||
&self.image
|
||||
}
|
||||
|
||||
|
|
@ -200,14 +205,14 @@ impl TextureAtlas {
|
|||
} else {
|
||||
let pos = [dirty.min_x, dirty.min_y];
|
||||
let size = [dirty.max_x - dirty.min_x, dirty.max_y - dirty.min_y];
|
||||
let region = self.image.region(pos, size);
|
||||
let region = self.image.region_by_pixels(pos, size);
|
||||
Some(ImageDelta::partial(pos, region, texture_options))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the coordinates of where the rect ended up,
|
||||
/// and invalidates the region.
|
||||
pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut FontImage) {
|
||||
pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut ColorImage) {
|
||||
/// On some low-precision GPUs (my old iPad) characters get muddled up
|
||||
/// if we don't add some empty pixels between the characters.
|
||||
/// On modern high-precision GPUs this is not needed.
|
||||
|
|
@ -254,13 +259,15 @@ impl TextureAtlas {
|
|||
}
|
||||
}
|
||||
|
||||
fn resize_to_min_height(image: &mut FontImage, required_height: usize) -> bool {
|
||||
fn resize_to_min_height(image: &mut ColorImage, required_height: usize) -> bool {
|
||||
while required_height >= image.height() {
|
||||
image.size[1] *= 2; // double the height
|
||||
}
|
||||
|
||||
if image.width() * image.height() > image.pixels.len() {
|
||||
image.pixels.resize(image.width() * image.height(), 0.0);
|
||||
image
|
||||
.pixels
|
||||
.resize(image.width() * image.height(), Color32::TRANSPARENT);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ pub enum TextureWrapMode {
|
|||
/// What has been allocated and freed during the last period.
|
||||
///
|
||||
/// These are commands given to the integration painter.
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[derive(Clone, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[must_use = "The painter must take care of this"]
|
||||
pub struct TexturesDelta {
|
||||
|
|
|
|||
Loading…
Reference in New Issue