/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{FontInstanceData, FontInstanceFlags, FontInstanceKey}; use api::{FontInstanceOptions, FontInstancePlatformOptions}; use api::{FontKey, FontRenderMode, FontSize, FontTemplate, FontVariation}; use api::{ColorU, GlyphIndex, GlyphDimensions, SyntheticItalics}; use api::{IdNamespace, BlobImageResources}; use api::channel::crossbeam::{unbounded, Receiver, Sender}; use api::units::*; use api::ImageFormat; usecrate::platform::font::FontContext; usecrate::profiler::GlyphRasterizeProfiler; usecrate::types::{FastHashMap, FastHashSet}; usecrate::telemetry::Telemetry; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use rayon::ThreadPool; use rayon::prelude::*; use euclid::approxeq::ApproxEq; use smallvec::SmallVec; use std::cmp; use std::cell::Cell; use std::hash::{Hash, Hasher}; use std::mem; use std::ops::Deref; use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak}; use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::atomic::{AtomicBool, Ordering};
impl FontContexts { /// Get access to the font context associated to the current thread. pubfn lock_current_context(&self) -> MutexGuard<FontContext> { matchself.current_worker_id() {
Some(id) => self.lock_context(id),
None => self.lock_any_context(),
}
}
// super simple random to avoid dependency on rand fn random() -> u32 {
SEED.with(|seed| {
seed.set(seed.get().wrapping_mul(22695477).wrapping_add(1));
seed.get()
})
}
// select glyphs that have not been requested yet. for key in glyph_keys { if !handle(key) { continue;
}
// Increment the total number of glyphs that are pending. This is used to determine // later whether to use worker threads for the remaining glyphs during resolve time. self.pending_glyph_count += 1; self.glyph_request_count += 1;
// Find a batch container for the font instance for this glyph. Use get_mut to avoid // cloning the font instance, since this is the common path. matchself.pending_glyph_requests.get_mut(&font) {
Some(container) => {
container.push(*key);
batch_size = container.len();
}
None => { // If no batch exists for this font instance, add the glyph to a new one. self.pending_glyph_requests.insert(
font.clone(),
smallvec![*key],
);
}
}
}
// If the batch for this font instance is big enough, kick off an async // job to start rasterizing these glyphs on other threads now. if batch_size >= GLYPH_BATCH_SIZE { let container = self.pending_glyph_requests.get_mut(&font).unwrap(); let glyphs = mem::replace(container, SmallVec::new()); self.flush_glyph_requests(font, glyphs, true);
}
}
/// Internal method to flush a list of glyph requests to a set of worker threads, /// or process on this thread if there isn't much work to do (in which case the /// overhead of processing these on a thread is unlikely to be a performance win). fn flush_glyph_requests(
&mutself,
font: FontInstance,
glyphs: SmallVec<[GlyphKey; 16]>,
use_workers: bool,
) { let font = Arc::new(font); let font_contexts = Arc::clone(&self.font_contexts); self.pending_glyph_jobs += glyphs.len(); self.pending_glyph_count -= glyphs.len();
let can_use_r8_format = self.can_use_r8_format;
// if the number of glyphs is small, do it inline to avoid the threading overhead; // send the result into glyph_tx so downstream code can't tell the difference. iflet Some(thread) = &self.dedicated_thread { let tx = self.glyph_tx.clone(); let _ = thread.tx.send(GlyphRasterMsg::Rasterize { font, glyphs, can_use_r8_format, tx });
} elseifself.enable_multithreading && use_workers { // spawn an async task to get off of the render backend thread as early as // possible and in that task use rayon's fork join dispatch to rasterize the // glyphs in the thread pool.
profile_scope!("spawning process_glyph jobs"); let tx = self.glyph_tx.clone(); self.workers.spawn(move || {
FontContext::begin_rasterize(&font); // If the FontContext supports distributing a font across multiple threads, // then use par_iter so different glyphs of the same font are processed on // multiple threads. if FontContext::distribute_across_threads() {
glyphs.par_iter().for_each(|key| { letmut context = font_contexts.lock_current_context(); let job_font = font.clone(); let job = process_glyph(&mut context, can_use_r8_format, job_font, *key);
tx.send(job).unwrap();
});
} else { // For FontContexts that prefer to localize a font to a single thread, // just process all the glyphs on the same worker to avoid contention. for key in glyphs { letmut context = font_contexts.lock_current_context(); let job_font = font.clone(); let job = process_glyph(&mut context, can_use_r8_format, job_font, key);
tx.send(job).unwrap();
}
}
FontContext::end_rasterize(&font);
});
} else {
FontContext::begin_rasterize(&font); for key in glyphs { letmut context = font_contexts.lock_current_context(); let job_font = font.clone(); let job = process_glyph(&mut context, can_use_r8_format, job_font, key); self.glyph_tx.send(job).unwrap();
}
FontContext::end_rasterize(&font);
}
}
// Work around the borrow checker, since we call flush_glyph_requests below letmut pending_glyph_requests = mem::replace(
&mutself.pending_glyph_requests,
FastHashMap::default(),
); // If we have a large amount of remaining work to do, spawn to worker threads, // even if that work is shared among a number of different font instances. let use_workers = self.pending_glyph_count >= 8; for (font, pending_glyphs) in pending_glyph_requests.drain() { self.flush_glyph_requests(
font,
pending_glyphs,
use_workers,
);
} // Restore this so that we don't heap allocate next frame self.pending_glyph_requests = pending_glyph_requests;
debug_assert_eq!(self.pending_glyph_count, 0);
debug_assert!(self.pending_glyph_requests.is_empty());
profile_scope!("resolve_glyphs"); // TODO: rather than blocking until all pending glyphs are available // we could try_recv and steal work from the thread pool to take advantage // of the fact that this thread is alive and we avoid the added latency // of blocking it. letmut jobs = {
profile_scope!("blocking wait on glyph_rx"); self.glyph_rx.iter().take(self.pending_glyph_jobs).collect::<Vec<_>>()
};
assert_eq!(jobs.len(), self.pending_glyph_jobs, "BUG: Didn't receive all pending glyphs!"); self.pending_glyph_jobs = 0;
// Ensure that the glyphs are always processed in the same // order for a given text run (since iterating a hash set doesn't // guarantee order). This can show up as very small float inaccuracy // differences in rasterizers due to the different coordinates // that text runs get associated with by the texture cache allocator.
jobs.sort_by(|a, b| (*a.font).cmp(&*b.font).then(a.key.cmp(&b.key)));
for job in jobs {
handle(job, self.can_use_r8_format);
}
// Now that we are done with the critical path (rendering the glyphs), // we can schedule removing the fonts if needed. self.remove_dead_fonts();
#[allow(dead_code)] pubfn determinant(&self) -> f64 { self.scale_x as f64 * self.scale_y as f64 - self.skew_y as f64 * self.skew_x as f64
}
#[allow(dead_code)] pubfn compute_scale(&self) -> Option<(f64, f64)> { let det = self.determinant(); if det != 0.0 { let x_scale = (self.scale_x as f64).hypot(self.skew_y as f64); let y_scale = det.abs() / x_scale;
Some((x_scale, y_scale))
} else {
None
}
}
pubfn get_subpx_dir(&self) -> SubpixelDirection { ifself.skew_y.approx_eq(&0.0) { // The X axis is not projected onto the Y axis
SubpixelDirection::Horizontal
} elseifself.scale_x.approx_eq(&0.0) { // The X axis has been swapped with the Y axis
SubpixelDirection::Vertical
} else { // Mixed transforms get no subpixel positioning
SubpixelDirection::None
}
}
}
// Some platforms (i.e. Windows) may have trouble rasterizing glyphs above this size. // Ensure glyph sizes are reasonably limited to avoid that scenario. pubconst FONT_SIZE_LIMIT: f32 = 320.0;
/// Immutable description of a font instance's shared state. /// /// `BaseFontInstance` can be identified by a `FontInstanceKey` to avoid hashing it. #[derive(Clone, Debug, Ord, PartialOrd, MallocSizeOf)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct BaseFontInstance { /// pub instance_key: FontInstanceKey, /// pub font_key: FontKey, /// pub size: FontSize, /// pub options: FontInstanceOptions, /// #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] pub platform_options: Option<FontInstancePlatformOptions>, /// pub variations: Vec<FontVariation>,
}
/// A shared map from fonts key local to a namespace to shared font keys that /// can be shared across many namespaces. Local keys are tracked in a hashmap /// that stores a strong reference per mapping so that their count can be /// tracked. A map of font templates is used to hash font templates to their /// final shared key. The shared key will stay alive so long as there are /// any strong references to the mapping entry. Care must be taken when /// clearing namespaces of shared keys as this may trigger shared font keys /// to expire which require individual processing. Shared font keys will be /// created within the provided unique namespace. #[derive(Clone)] pubstruct FontKeyMap(Arc<RwLock<FontKeyMapLocked>>);
pubfn delete_key(&mutself, font_key: &FontKey) -> Option<FontKey> { letmut locked = self.lock_mut(); let mapped = match locked.key_map.remove(font_key) {
Some(mapped) => mapped,
None => return Some(*font_key),
}; if Arc::strong_count(&mapped) <= 2 { // Only the last mapped key and template map point to it.
locked.template_map.remove(&mapped.template);
Some(mapped.font_key)
} else {
None
}
}
pubfn clear_namespace(&mutself, namespace: IdNamespace) -> Vec<FontKey> { letmut locked = self.lock_mut();
locked.key_map.retain(|key, _| { if key.0 == namespace { false
} else { true
}
}); letmut deleted_keys = Vec::new();
locked.template_map.retain(|_, mapped| { if Arc::strong_count(mapped) <= 1 { // Only the template map points to it.
deleted_keys.push(mapped.font_key); false
} else { true
}
});
deleted_keys
}
}
type FontTemplateMapLocked = FastHashMap<FontKey, FontTemplate>;
/// A map of font keys to font templates that might hold both namespace-local /// font templates as well as shared templates. #[derive(Clone)] pubstruct FontTemplateMap(Arc<RwLock<FontTemplateMapLocked>>);
/// A map of namespace-local font instance keys to shared keys. Weak references /// are used to track the liveness of each key mapping as other consumers of /// BaseFontInstance might hold strong references to the entry. A mapping from /// BaseFontInstance to the shared key is then used to determine which shared /// key to assign to that instance. When the weak count of the mapping is zero, /// the entry is allowed to expire. Again, care must be taken when clearing /// a namespace within the key map as it may cause shared key expirations that /// require individual processing. Shared instance keys will be created within /// the provided unique namespace. #[derive(Clone)] pubstruct FontInstanceKeyMap(Arc<RwLock<FontInstanceKeyMapLocked>>);
pubfn delete_key(&mutself, key: &FontInstanceKey) -> Option<FontInstanceKey> { letmut locked = self.lock_mut(); let mapped = match locked.key_map.remove(key).and_then(|weak| weak.upgrade()) {
Some(mapped) => mapped,
None => return Some(*key),
}; if Arc::weak_count(&mapped) == 0 { // Only the instance set points to it.
locked.instances.remove(&mapped);
Some(mapped.instance_key)
} else {
None
}
}
pubfn clear_namespace(&mutself, namespace: IdNamespace) -> Vec<FontInstanceKey> { letmut locked = self.lock_mut();
locked.key_map.retain(|key, _| { if key.0 == namespace { false
} else { true
}
}); letmut deleted_keys = Vec::new();
locked.instances.retain(|mapped| { if Arc::weak_count(mapped) == 0 { // Only the instance set points to it.
deleted_keys.push(mapped.instance_key); false
} else { true
}
});
deleted_keys
}
}
type FontInstanceMapLocked = FastHashMap<FontInstanceKey, Arc<BaseFontInstance>>;
/// A map of font instance data accessed concurrently from multiple threads. #[derive(Clone)] pubstruct FontInstanceMap(Arc<RwLock<FontInstanceMapLocked>>);
/// Shared font resources that may need to be passed between multiple threads /// such as font templates and font instances. They are individually protected /// by locks to ensure safety. #[derive(Clone)] pubstruct SharedFontResources { pub templates: FontTemplateMap, pub instances: FontInstanceMap, pub font_keys: FontKeyMap, pub instance_keys: FontInstanceKeyMap,
}
/// A mutable font instance description. /// /// Performance is sensitive to the size of this structure, so it should only contain /// the fields that we need to modify from the original base font instance. #[derive(Clone, Debug, Ord, PartialOrd)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct FontInstance { pub base: Arc<BaseFontInstance>, pub transform: FontTransform, pub render_mode: FontRenderMode, pub flags: FontInstanceFlags, pub color: ColorU, // The font size is in *device/raster* pixels, not logical pixels. // It is stored as an f32 since we need sub-pixel sizes. pub size: FontSize,
}
impl Hash for FontInstance { fn hash<H: Hasher>(&self, state: &mut H) { // Hash only the base instance's key to avoid the cost of hashing // the rest. self.base.instance_key.hash(state); self.transform.hash(state); self.render_mode.hash(state); self.flags.hash(state); self.color.hash(state); self.size.hash(state);
}
}
impl PartialEq for FontInstance { fn eq(&self, other: &FontInstance) -> bool { // Compare only the base instance's key. self.base.instance_key == other.base.instance_key && self.transform == other.transform && self.render_mode == other.render_mode && self.flags == other.flags && self.color == other.color && self.size == other.size
}
} impl Eq for FontInstance {}
impl Deref for FontInstance { type Target = BaseFontInstance; fn deref(&self) -> &BaseFontInstance { self.base.as_ref()
}
}
impl SubpixelDirection { // Limit the subpixel direction to what is supported by the glyph format. pubfn limit_by(self, glyph_format: GlyphFormat) -> Self { match glyph_format {
GlyphFormat::Bitmap |
GlyphFormat::ColorBitmap => SubpixelDirection::None,
_ => self,
}
}
impl SubpixelOffset { // Skia quantizes subpixel offsets into 1/4 increments. // Given the absolute position, return the quantized increment fn quantize(pos: f32) -> Self { // Following the conventions of Gecko and Skia, we want // to quantize the subpixel position, such that abs(pos) gives: // [0.0, 0.125) -> Zero // [0.125, 0.375) -> Quarter // [0.375, 0.625) -> Half // [0.625, 0.875) -> ThreeQuarters, // [0.875, 1.0) -> Zero // The unit tests below check for this. let apos = ((pos - pos.floor()) * 8.0) as i32;
impl GlyphFormat { /// Returns the ImageFormat that a glyph should be stored as in the texture cache. /// can_use_r8_format should be set false on platforms where we have encountered /// issues with R8 textures, so that we do not use them for glyphs. pubfn image_format(&self, can_use_r8_format: bool) -> ImageFormat { match *self {
GlyphFormat::Alpha |
GlyphFormat::TransformedAlpha |
GlyphFormat::Bitmap => { if can_use_r8_format {
ImageFormat::R8
} else {
ImageFormat::BGRA8
}
}
GlyphFormat::Subpixel |
GlyphFormat::TransformedSubpixel |
GlyphFormat::ColorBitmap => ImageFormat::BGRA8,
}
}
}
#[allow(dead_code)] #[inline] fn blend_strike_pixel(dest: u8, src: u32, src_alpha: u32) -> u8 { // Assume premultiplied alpha such that src and dest are already multiplied // by their respective alpha values and in range 0..=255. The rounded over // blend is then (src * 255 + dest * (255 - src_alpha) + 128) / 255. // We approximate (x + 128) / 255 as (x + 128 + ((x + 128) >> 8)) >> 8. let x = src * 255 + dest as u32 * (255 - src_alpha) + 128;
((x + (x >> 8)) >> 8) as u8
}
// Blends a single strike at a given offset into a destination buffer, assuming // the destination has been allocated with enough extra space to accommodate the // offset. #[allow(dead_code)] fn blend_strike(
dest_bitmap: &mut [u8],
src_bitmap: &[u8],
width: usize,
height: usize,
subpixel_mask: bool,
offset: f64,
) { let dest_stride = dest_bitmap.len() / height; let src_stride = width * 4; let offset_integer = offset.floor() as usize * 4; let offset_fract = (offset.fract() * 256.0) as u32; for (src_row, dest_row) in src_bitmap.chunks(src_stride).zip(dest_bitmap.chunks_mut(dest_stride)) { letmut prev_px = [0u32; 4]; let dest_row_offset = &mut dest_row[offset_integer .. offset_integer + src_stride]; for (src, dest) in src_row.chunks(4).zip(dest_row_offset.chunks_mut(4)) { let px = [src[0] as u32, src[1] as u32, src[2] as u32, src[3] as u32]; // Blend current pixel with previous pixel based on fractional offset. let next_px = [px[0] * offset_fract,
px[1] * offset_fract,
px[2] * offset_fract,
px[3] * offset_fract]; let offset_px = [(((px[0] << 8) - next_px[0]) + prev_px[0] + 128) >> 8,
(((px[1] << 8) - next_px[1]) + prev_px[1] + 128) >> 8,
(((px[2] << 8) - next_px[2]) + prev_px[2] + 128) >> 8,
(((px[3] << 8) - next_px[3]) + prev_px[3] + 128) >> 8]; if subpixel_mask { // Subpixel masks assume each component is an independent weight.
dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[0]);
dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[1]);
dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[2]);
dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]);
} else { // Otherwise assume we have a premultiplied alpha BGRA value.
dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[3]);
dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[3]);
dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[3]);
dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]);
} // Save the remainder for blending onto the next pixel.
prev_px = next_px;
} if offset_fract > 0 { // When there is fractional offset, there will be a remaining value // from the previous pixel but no next pixel, so just use that. let dest = &mut dest_row[offset_integer + src_stride .. ]; let offset_px = [(prev_px[0] + 128) >> 8,
(prev_px[1] + 128) >> 8,
(prev_px[2] + 128) >> 8,
(prev_px[3] + 128) >> 8]; if subpixel_mask {
dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[0]);
dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[1]);
dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[2]);
dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]);
} else {
dest[0] = blend_strike_pixel(dest[0], offset_px[0], offset_px[3]);
dest[1] = blend_strike_pixel(dest[1], offset_px[1], offset_px[3]);
dest[2] = blend_strike_pixel(dest[2], offset_px[2], offset_px[3]);
dest[3] = blend_strike_pixel(dest[3], offset_px[3], offset_px[3]);
}
}
}
}
// Applies multistrike bold to a source bitmap. This assumes the source bitmap // is a tighly packed slice of BGRA pixel values of exactly the specified width // and height. The specified extra strikes and pixel step control where to put // each strike. The pixel step is allowed to have a fractional offset and does // not strictly need to be integer. #[allow(dead_code)] pubfn apply_multistrike_bold(
src_bitmap: &[u8],
width: usize,
height: usize,
subpixel_mask: bool,
extra_strikes: usize,
pixel_step: f64,
) -> (Vec<u8>, usize) { let src_stride = width * 4; // The amount of extra width added to the bitmap from the extra strikes. let extra_width = (extra_strikes as f64 * pixel_step).ceil() as usize; let dest_width = width + extra_width; let dest_stride = dest_width * 4; // Zero out the initial bitmap so any extra width is cleared. letmut dest_bitmap = vec![0u8; dest_stride * height]; for (src_row, dest_row) in src_bitmap.chunks(src_stride).zip(dest_bitmap.chunks_mut(dest_stride)) { // Copy the initial bitmap strike rows directly from the source.
dest_row[0 .. src_stride].copy_from_slice(src_row);
} // Finally blend each extra strike in turn. for i in1 ..= extra_strikes { let offset = i as f64 * pixel_step;
blend_strike(&mut dest_bitmap, src_bitmap, width, height, subpixel_mask, offset);
}
(dest_bitmap, dest_width)
}
impl RasterizedGlyph { #[allow(dead_code)] pubfn downscale_bitmap_if_required(&mutself, font: &FontInstance) { // Check if the glyph is going to be downscaled in the shader. If the scaling is // less than 0.5, that means bilinear filtering can't effectively filter the glyph // without aliasing artifacts. // // Instead of fixing this by mipmapping the glyph cache texture, rather manually // produce the appropriate mip level for individual glyphs where bilinear filtering // will still produce acceptable results. matchself.format {
GlyphFormat::Bitmap | GlyphFormat::ColorBitmap => {},
_ => return,
} let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); let upscaled = x_scale.max(y_scale) as f32; letmut new_scale = self.scale; if new_scale * upscaled <= 0.0 { return;
} letmut steps = 0; while new_scale * upscaled <= 0.5 {
new_scale *= 2.0;
steps += 1;
} // If no mipping is necessary, just bail. if steps == 0 { return;
}
// Calculate the actual size of the mip level. let new_width = (self.width as usize + (1 << steps) - 1) >> steps; let new_height = (self.height as usize + (1 << steps) - 1) >> steps; letmut new_bytes: Vec<u8> = Vec::with_capacity(new_width * new_height * 4);
// Produce destination pixels by applying a box filter to the source pixels. // The box filter corresponds to how graphics drivers may generate mipmaps. for y in0 .. new_height { for x in0 .. new_width { // Calculate the number of source samples that contribute to the destination pixel. let src_y = y << steps; let src_x = x << steps; let y_samples = (1 << steps).min(self.height as usize - src_y); let x_samples = (1 << steps).min(self.width as usize - src_x); let num_samples = (x_samples * y_samples) as u32;
letmut src_idx = (src_y * self.width as usize + src_x) * 4; // Initialize the accumulator with half an increment so that when later divided // by the sample count, it will effectively round the accumulator to the nearest // increment. letmut accum = [num_samples / 2; 4]; // Accumulate all the contributing source sampless. for _ in0 .. y_samples { for _ in0 .. x_samples {
accum[0] += self.bytes[src_idx + 0] as u32;
accum[1] += self.bytes[src_idx + 1] as u32;
accum[2] += self.bytes[src_idx + 2] as u32;
accum[3] += self.bytes[src_idx + 3] as u32;
src_idx += 4;
}
src_idx += (self.width as usize - x_samples) * 4;
}
// Finally, divide by the sample count to get the mean value for the new pixel.
new_bytes.extend_from_slice(&[
(accum[0] / num_samples) as u8,
(accum[1] / num_samples) as u8,
(accum[2] / num_samples) as u8,
(accum[3] / num_samples) as u8,
]);
}
}
// Fix the bounds for the new glyph data. self.top /= (1 << steps) as f32; self.left /= (1 << steps) as f32; self.width = new_width as i32; self.height = new_height as i32; self.scale = new_scale; self.bytes = new_bytes;
}
}
pubstruct FontContexts { // These worker are mostly accessed from their corresponding worker threads. // The goal is that there should be no noticeable contention on the mutexes.
worker_contexts: Vec<Mutex<FontContext>>, // Stored here as a convenience to get the current thread index. #[allow(dead_code)]
workers: Arc<ThreadPool>,
locked_mutex: Mutex<bool>,
locked_cond: Condvar,
}
impl FontContexts { /// Get access to any particular font context. /// /// The id is an index between 0 and num_worker_contexts for font contexts /// associated to the thread pool. pubfn lock_context(&self, id: usize) -> MutexGuard<FontContext> { self.worker_contexts[id].lock().unwrap()
}
// Find a context that is currently unlocked to use, otherwise defaulting // to the first context. pubfn lock_any_context(&self) -> MutexGuard<FontContext> { for context in &self.worker_contexts { iflet Ok(mutex) = context.try_lock() { return mutex;
}
} self.lock_context(0)
}
// number of contexts associated to workers pubfn num_worker_contexts(&self) -> usize { self.worker_contexts.len()
}
}
// Arc that can be safely moved into a spawn closure. let font_contexts = self.clone(); // Spawn a new thread on which to run the for-each off the main thread. self.workers.spawn(move || { // Lock the shared and worker contexts up front. letmut locks = Vec::with_capacity(font_contexts.num_worker_contexts()); for i in0 .. font_contexts.num_worker_contexts() {
locks.push(font_contexts.lock_context(i));
}
// Signal the locked condition now that all contexts are locked.
*font_contexts.locked_mutex.lock().unwrap() = true;
font_contexts.locked_cond.notify_all();
// Now that everything is locked, proceed to processing each locked context. for context in locks {
f(context);
}
});
// Wait for locked condition before resuming. Safe to proceed thereafter // since any other thread that needs to use a FontContext will try to lock // it first. while !*locked {
locked = self.locked_cond.wait(locked).unwrap();
}
}
}
/// The current set of loaded fonts.
fonts: FastHashSet<FontKey>,
/// The current number of individual glyphs waiting in pending batches.
pending_glyph_count: usize,
/// The current number of glyph request jobs that have been kicked to worker threads.
pending_glyph_jobs: usize,
/// The number of glyphs requested this frame.
glyph_request_count: usize,
/// A map of current glyph request batches.
pending_glyph_requests: FastHashMap<FontInstance, SmallVec<[GlyphKey; 16]>>,
// Receives the rendered glyphs.
glyph_rx: Receiver<GlyphRasterJob>,
glyph_tx: Sender<GlyphRasterJob>,
// We defer removing fonts to the end of the frame so that: // - this work is done outside of the critical path, // - we don't have to worry about the ordering of events if a font is used on // a frame where it is used (although it seems unlikely).
fonts_to_remove: Vec<FontKey>, // Defer removal of font instances, as for fonts.
font_instances_to_remove: Vec<FontInstance>,
// Whether to parallelize glyph rasterization with rayon.
enable_multithreading: bool,
// Whether glyphs can be rasterized in r8 format when it makes sense.
can_use_r8_format: bool,
}
// Quantize the transform to minimize thrashing of the glyph cache, but // only quantize the transform when preparing to access the glyph cache. // This way, the glyph subpixel positions, which are calculated before // this, can still use the precise transform which is required to match // the subpixel positions computed for glyphs in the text run shader.
font.transform = font.transform.quantize();
}
profile_scope!("remove_dead_fonts"); letmut fonts_to_remove = mem::replace(& mutself.fonts_to_remove, Vec::new()); // Only remove font from FontContexts if previously added.
fonts_to_remove.retain(|font| self.fonts.remove(font)); let font_instances_to_remove = mem::replace(& mutself.font_instances_to_remove, Vec::new()); iflet Some(thread) = &self.dedicated_thread { for font_key in fonts_to_remove { let _ = thread.tx.send(GlyphRasterMsg::DeleteFont { font_key });
} for instance in font_instances_to_remove { let _ = thread.tx.send(GlyphRasterMsg::DeleteFontInstance { instance });
}
} else { self.font_contexts.async_for_each(move |mut context| { for font_key in &fonts_to_remove {
context.delete_font(font_key);
} for instance in &font_instances_to_remove {
context.delete_font_instance(instance);
}
});
}
}
#[cfg(feature = "replay")] pubfn reset(&mutself) { //TODO: any signals need to be sent to the workers? self.pending_glyph_jobs = 0; self.pending_glyph_count = 0; self.glyph_request_count = 0; self.fonts_to_remove.clear(); self.font_instances_to_remove.clear();
}
}
fn pack_glyph_variants_horizontal(variants: &[RasterizedGlyph]) -> RasterizedGlyph { // Pack 4 glyph variants horizontally into a single texture. // Normalize both left and top offsets via padding so all variants can use the same base offsets.
let min_left = variants.iter().map(|v| v.left.floor()).fold(f32::INFINITY, f32::min); let max_top = variants.iter().map(|v| v.top.floor()).fold(f32::NEG_INFINITY, f32::max);
// Slot width must accommodate the widest variant plus left padding let slot_width = variants.iter()
.map(|v| v.width + (v.left.floor() - min_left) as i32)
.max().unwrap();
// Slot height must accommodate the tallest variant plus top padding let slot_height = variants.iter()
.map(|v| v.height + (max_top - v.top.floor()) as i32)
.max().unwrap();
for (variant_idx, variant) in variants.iter().enumerate() { // Compute padding needed to normalize both left and top offsets let left_pad = (variant.left.floor() - min_left) as i32; let top_pad = (max_top - variant.top.floor()) as i32; let slot_x = variant_idx as i32 * slot_width;
for src_y in0..variant.height { let dst_y = src_y + top_pad; if dst_y >= slot_height { break;
}
let dst_x = slot_x + left_pad; if dst_x < 0 { continue;
}
let src_row_start = (src_y * variant.width * bpp) as usize; let src_row_end = src_row_start + (variant.width * bpp) as usize; let dst_row_start = (dst_y * packed_width * bpp + dst_x * bpp) as usize; let dst_row_end = dst_row_start + (variant.width * bpp) as usize;
iflet Ok(refmut glyph) = job.result { // Sanity check. let bpp = 4; // We always render glyphs in 32 bits RGBA format.
assert_eq!(
glyph.bytes.len(),
bpp * (glyph.width * glyph.height) as usize
);
// a quick-and-dirty monochrome over fn over(dst: u8, src: u8) -> u8 { let a = src as u32; let a = 256 - a; let dst = ((dst as u32 * a) >> 8) as u8;
src + dst
}
if GLYPH_FLASHING.load(Ordering::Relaxed) { let color = (random() & 0xff) as u8; for i in &mut glyph.bytes {
*i = over(*i, color);
}
}
// Check if the glyph has a bitmap that needs to be downscaled.
glyph.downscale_bitmap_if_required(&job.font);
// Convert from BGRA8 to R8 if required. In the future we can make it the // backends' responsibility to output glyphs in the desired format, // potentially reducing the number of copies. if glyph.format.image_format(can_use_r8_format).bytes_per_pixel() == 1 {
glyph.bytes = glyph.bytes
.chunks_mut(4)
.map(|pixel| pixel[3])
.collect::<Vec<_>>();
}
}
#[test] fn rasterize_200_glyphs() { // This test loads a font from disc, the renders 4 requests containing // 50 glyphs each, deletes the font and waits for the result.
use rayon::ThreadPoolBuilder; use std::fs::File; use std::io::Read; use api::{FontKey, FontInstanceKey, FontTemplate, IdNamespace}; use api::units::DevicePoint; use std::sync::Arc; usecrate::rasterizer::{FontInstance, BaseFontInstance, GlyphKey, GlyphRasterizer};
let worker = ThreadPoolBuilder::new()
.thread_name(|idx|{ format!("WRWorker#{}", idx) })
.build(); let workers = Arc::new(worker.unwrap()); letmut glyph_rasterizer = GlyphRasterizer::new(workers, None, true); letmut font_file =
File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file"); letmut font_data = vec![];
font_file
.read_to_end(&mut font_data)
.expect("failed to read font file");
let font_key = FontKey::new(IdNamespace(0), 0);
glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
let font = FontInstance::from_base(Arc::new(BaseFontInstance::new(
FontInstanceKey::new(IdNamespace(0), 0),
font_key, 32.0,
None,
None,
Vec::new(),
)));
let subpx_dir = font.get_subpx_dir();
letmut glyph_keys = Vec::with_capacity(200); for i in0 .. 200 {
glyph_keys.push(GlyphKey::new(
i,
DevicePoint::zero(),
subpx_dir,
));
}
for i in0 .. 4 {
glyph_rasterizer.request_glyphs(
font.clone(),
&glyph_keys[(50 * i) .. (50 * (i + 1))],
|_| true,
);
}
#[test] fn rasterize_large_glyphs() { // This test loads a font from disc and rasterize a few glyphs with a size of 200px to check // that the texture cache handles them properly. use rayon::ThreadPoolBuilder; use std::fs::File; use std::io::Read; use api::{FontKey, FontInstanceKey, FontTemplate, IdNamespace}; use api::units::DevicePoint; use std::sync::Arc; usecrate::rasterizer::{FontInstance, BaseFontInstance, GlyphKey, GlyphRasterizer};
let worker = ThreadPoolBuilder::new()
.thread_name(|idx|{ format!("WRWorker#{}", idx) })
.build(); let workers = Arc::new(worker.unwrap()); letmut glyph_rasterizer = GlyphRasterizer::new(workers, None, true); letmut font_file =
File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file"); letmut font_data = vec![];
font_file
.read_to_end(&mut font_data)
.expect("failed to read font file");
let font_key = FontKey::new(IdNamespace(0), 0);
glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
let font = FontInstance::from_base(Arc::new(BaseFontInstance::new(
FontInstanceKey::new(IdNamespace(0), 0),
font_key, 200.0,
None,
None,
Vec::new(),
)));
let subpx_dir = font.get_subpx_dir();
letmut glyph_keys = Vec::with_capacity(10); for i in0 .. 10 {
glyph_keys.push(GlyphKey::new(
i,
DevicePoint::zero(),
subpx_dir,
));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.