From 8a99dbf61cbc7026096c467051f0d4843bc1a39e Mon Sep 17 00:00:00 2001 From: Mason Chang Date: Tue, 24 Jan 2017 16:55:17 -0800 Subject: [PATCH 1/2] Quantize glyph positions for subpixel text positioning. --- webrender/src/prim_store.rs | 37 +++++++++++---- webrender/src/resource_cache.rs | 82 +++++++++++++-------------------- webrender/src/tiling.rs | 22 +++++++-- webrender_traits/src/types.rs | 56 +++++++++++++++++++++- 4 files changed, 134 insertions(+), 63 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 278af3369c..3abc922764 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -18,8 +18,8 @@ use webrender_traits::{FontKey, FontRenderMode, WebGLContextId}; use webrender_traits::{device_length, DeviceIntRect, DeviceIntSize}; use webrender_traits::{DeviceRect, DevicePoint, DeviceSize}; use webrender_traits::{LayerRect, LayerSize, LayerPoint}; -use webrender_traits::LayerToWorldTransform; -use webrender_traits::{GlyphOptions}; +use webrender_traits::{LayerToWorldTransform, GlyphOptions}; +use tiling::{AuxiliaryListsMap, StackingContext}; pub const CLIP_DATA_GPU_SIZE: usize = 5; pub const MASK_DATA_GPU_SIZE: usize = 1; @@ -292,6 +292,7 @@ pub struct TextRunPrimitiveCpu { pub render_mode: FontRenderMode, pub resource_address: GpuStoreAddress, pub glyph_options: Option, + pub stackingcontext_index: usize, } #[derive(Debug, Clone)] @@ -718,7 +719,9 @@ impl PrimitiveStore { pub fn resolve_primitives(&mut self, resource_cache: &ResourceCache, - device_pixel_ratio: f32) -> Vec { + device_pixel_ratio: f32, + layer_store: &Vec, + auxiliary_lists_map: &AuxiliaryListsMap) -> Vec { let mut deferred_resolves = Vec::new(); for prim_index in self.prims_to_resolve.drain(..) { @@ -735,14 +738,22 @@ impl PrimitiveStore { PrimitiveKind::RadialGradient=> {} PrimitiveKind::TextRun => { let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0]; + let sc_index = text.stackingcontext_index; + let font_size_dp = text.logical_font_size.scale_by(device_pixel_ratio); let dest_rects = self.gpu_resource_rects.get_slice_mut(text.resource_address, text.glyph_range.length); + + let layer = &layer_store[sc_index]; + let auxiliary_lists = auxiliary_lists_map.get(&layer.pipeline_id) + .expect("No auxiliary lists?"); + let src_glyphs = auxiliary_lists.glyph_instances(&text.glyph_range); + let texture_id = resource_cache.get_glyphs(text.font_key, font_size_dp, text.color, - &text.glyph_indices, + src_glyphs, text.render_mode, text.glyph_options, |index, uv0, uv1| { let dest_rect = &mut dest_rects[index]; @@ -878,7 +889,8 @@ impl PrimitiveStore { resource_cache: &mut ResourceCache, layer_transform: &LayerToWorldTransform, device_pixel_ratio: f32, - auxiliary_lists: &AuxiliaryLists) -> bool { + auxiliary_lists: &AuxiliaryLists, + stackingcontext_index: usize) -> bool { let metadata = &mut self.cpu_metadata[prim_index.0]; let mut prim_needs_resolve = false; @@ -918,7 +930,10 @@ impl PrimitiveStore { } PrimitiveKind::TextRun => { let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0]; + text.stackingcontext_index = stackingcontext_index; + let font_size_dp = text.logical_font_size.scale_by(device_pixel_ratio); + let src_glyphs = auxiliary_lists.glyph_instances(&text.glyph_range); prim_needs_resolve = true; if text.cache_dirty { @@ -927,18 +942,24 @@ impl PrimitiveStore { debug_assert!(metadata.gpu_data_count == text.glyph_range.length as i32); debug_assert!(text.glyph_indices.is_empty()); - let src_glyphs = auxiliary_lists.glyph_instances(&text.glyph_range); + let dest_glyphs = self.gpu_data16.get_slice_mut(metadata.gpu_data_address, text.glyph_range.length); let mut glyph_key = GlyphKey::new(text.font_key, font_size_dp, text.color, - src_glyphs[0].index); + src_glyphs[0].index, + src_glyphs[0].x, + src_glyphs[0].y, + text.render_mode, + text.glyph_options); let mut local_rect = LayerRect::zero(); let mut actual_glyph_count = 0; for src in src_glyphs { glyph_key.index = src.index; + glyph_key.set_x_offset(src.x); + glyph_key.set_y_offset(src.y); let dimensions = match resource_cache.get_glyph_dimensions(&glyph_key) { None => continue, @@ -999,7 +1020,7 @@ impl PrimitiveStore { resource_cache.request_glyphs(text.font_key, font_size_dp, text.color, - &text.glyph_indices, + src_glyphs, text.render_mode, text.glyph_options); } diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index 2a0a19ac3e..65355b1b13 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -22,12 +22,12 @@ use texture_cache::{TextureCache, TextureCacheItemId}; use webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageFormat, ImageRendering}; use webrender_traits::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId}; use webrender_traits::{DevicePoint, DeviceIntSize, ImageDescriptor, ColorF}; -use webrender_traits::{ExternalImageId, GlyphOptions}; +use webrender_traits::{ExternalImageId, GlyphOptions, GlyphInstance}; use threadpool::ThreadPool; thread_local!(pub static FONT_CONTEXT: RefCell = RefCell::new(FontContext::new())); -type GlyphCache = ResourceClassCache>; +type GlyphCache = ResourceClassCache>; /// Message sent from the resource cache to the glyph cache thread. enum GlyphCacheMsg { @@ -36,7 +36,7 @@ enum GlyphCacheMsg { /// Add a new font. AddFont(FontKey, FontTemplate), /// Request glyphs for a text run. - RequestGlyphs(FontKey, Au, ColorF, Vec, FontRenderMode, Option), + RequestGlyphs(FontKey, Au, ColorF, Vec, FontRenderMode, Option), /// Finished requesting glyphs. Reply with new glyphs. EndFrame, } @@ -62,28 +62,6 @@ pub struct CacheItem { pub uv1: DevicePoint, } -#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)] -pub struct RenderedGlyphKey { - pub key: GlyphKey, - pub render_mode: FontRenderMode, - pub glyph_options: Option, -} - -impl RenderedGlyphKey { - pub fn new(font_key: FontKey, - size: Au, - color: ColorF, - index: u32, - render_mode: FontRenderMode, - glyph_options: Option) -> RenderedGlyphKey { - RenderedGlyphKey { - key: GlyphKey::new(font_key, size, color, index), - render_mode: render_mode, - glyph_options: glyph_options, - } - } -} - pub struct ImageProperties { pub descriptor: ImageDescriptor, pub external_id: Option, @@ -175,7 +153,7 @@ struct ImageRequest { } struct GlyphRasterJob { - key: RenderedGlyphKey, + key: GlyphKey, result: Option, } @@ -330,7 +308,7 @@ impl ResourceCache { key: FontKey, size: Au, color: ColorF, - glyph_indices: &[u32], + glyph_instances: &[GlyphInstance], render_mode: FontRenderMode, glyph_options: Option) { debug_assert!(self.state == State::AddResources); @@ -341,7 +319,7 @@ impl ResourceCache { let msg = GlyphCacheMsg::RequestGlyphs(key, size, color, - glyph_indices.to_vec(), + glyph_instances.to_vec(), render_mode, glyph_options); self.glyph_cache_tx.send(msg).unwrap(); @@ -359,22 +337,26 @@ impl ResourceCache { font_key: FontKey, size: Au, color: ColorF, - glyph_indices: &[u32], + glyph_instances: &[GlyphInstance], render_mode: FontRenderMode, glyph_options: Option, mut f: F) -> SourceTexture where F: FnMut(usize, DevicePoint, DevicePoint) { debug_assert!(self.state == State::QueryResources); let cache = self.cached_glyphs.as_ref().unwrap(); let render_mode = self.get_glyph_render_mode(render_mode); - let mut glyph_key = RenderedGlyphKey::new(font_key, - size, - color, - 0, - render_mode, - glyph_options); + let mut glyph_key = GlyphKey::new(font_key, + size, + color, + 0, + 0.0, 0.0, + render_mode, + glyph_options); let mut texture_id = None; - for (loop_index, glyph_index) in glyph_indices.iter().enumerate() { - glyph_key.key.index = *glyph_index; + for (loop_index, glyph_instance) in glyph_instances.iter().enumerate() { + glyph_key.index = glyph_instance.index; + glyph_key.set_x_offset(glyph_instance.x); + glyph_key.set_y_offset(glyph_instance.y); + let image_id = cache.get(&glyph_key, self.current_frame_id); let cache_item = image_id.map(|image_id| self.texture_cache.get(image_id)); if let Some(cache_item) = cache_item { @@ -675,20 +657,22 @@ fn spawn_glyph_cache_thread() -> (Sender, Receiver { + GlyphCacheMsg::RequestGlyphs(key, size, color, glyph_instances, render_mode, glyph_options) => { // Request some glyphs for a text run. // For any glyph that isn't currently in the cache, // immeediately push a job to the worker thread pool // to start rasterizing this glyph now! let glyph_cache = glyph_cache.as_mut().unwrap(); - for glyph_index in indices { - let glyph_key = RenderedGlyphKey::new(key, - size, - color, - glyph_index, - render_mode, - glyph_options); + for glyph_instance in glyph_instances { + let glyph_key = GlyphKey::new(key, + size, + color, + glyph_instance.index, + glyph_instance.x, + glyph_instance.y, + render_mode, + glyph_options); glyph_cache.mark_as_needed(&glyph_key, current_frame_id); if !glyph_cache.contains_key(&glyph_key) && @@ -698,10 +682,10 @@ fn spawn_glyph_cache_thread() -> (Sender, Receiver, } impl GlyphKey { pub fn new(font_key: FontKey, size: Au, color: ColorF, - index: u32) -> GlyphKey { - GlyphKey { + index: u32, + x: f32, + y: f32, + render_mode: FontRenderMode, + glyph_options: Option) -> GlyphKey { + let mut key = GlyphKey { font_key: font_key, size: size, color: ColorU::from(color), index: index, + x_suboffset: SubpixelOffset::Zero, + y_suboffset: SubpixelOffset::Zero, + render_mode: render_mode, + glyph_options: glyph_options, + }; + + key.set_x_offset(x); + key.set_y_offset(y); + + key + } + + pub fn set_x_offset(&mut self, x: f32) { + self.x_suboffset = self.subpixel_quantize_offset(x); + } + + pub fn set_y_offset(&mut self, y: f32) { + self.y_suboffset = self.subpixel_quantize_offset(y); + } + + // Skia quantizes subpixel offets into 1/4 increments. + // Given the absolute position, return the quantized increment + fn subpixel_quantize_offset(&self, pos: f32) -> SubpixelOffset { + if self.render_mode != FontRenderMode::Subpixel { + return SubpixelOffset::Zero; + } + + const SUBPIXEL_ROUNDING :f32 = 0.125; // Skia chosen value. + let fraction = (pos + SUBPIXEL_ROUNDING).fract(); + + match fraction { + 0.0...0.25 => SubpixelOffset::Zero, + 0.25...0.5 => SubpixelOffset::Quarter, + 0.5...0.75 => SubpixelOffset::Half, + 0.75...1.0 => SubpixelOffset::ThreeQuarters, + _ => panic!("Should only be given the fractional part"), } } } From db6c4b056be79d9e188a874e50e2fe52394a84dc Mon Sep 17 00:00:00 2001 From: Mason Chang Date: Thu, 26 Jan 2017 09:03:07 -0800 Subject: [PATCH 2/2] subpixel text positioning for cg --- webrender/src/platform/macos/font.rs | 80 ++++++++++++++++++++-------- webrender/src/platform/unix/font.rs | 6 ++- webrender/src/resource_cache.rs | 7 ++- webrender_traits/src/types.rs | 11 ++++ 4 files changed, 80 insertions(+), 24 deletions(-) diff --git a/webrender/src/platform/macos/font.rs b/webrender/src/platform/macos/font.rs index accfae2b69..001a15083e 100644 --- a/webrender/src/platform/macos/font.rs +++ b/webrender/src/platform/macos/font.rs @@ -15,7 +15,8 @@ use core_text::font_descriptor::kCTFontDefaultOrientation; use core_text; use std::collections::HashMap; use std::collections::hash_map::Entry; -use webrender_traits::{ColorU, FontKey, FontRenderMode, GlyphDimensions, GlyphOptions}; +use webrender_traits::{ColorU, FontKey, FontRenderMode, GlyphDimensions}; +use webrender_traits::{GlyphOptions, SubpixelOffset}; pub type NativeFontHandle = CGFont; @@ -69,23 +70,47 @@ fn supports_subpixel_aa() -> bool { data[0] != data[1] || data[1] != data[2] } -fn get_glyph_metrics(ct_font: &CTFont, glyph: CGGlyph) -> GlyphMetrics { +fn get_glyph_metrics(ct_font: &CTFont, + glyph: CGGlyph, + x_subpixel: SubpixelOffset, + y_subpixel: SubpixelOffset) -> GlyphMetrics { let bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]); - let rasterized_left = bounds.origin.x.floor() as i32; - let rasterized_width = - (bounds.origin.x - (rasterized_left as f64) + bounds.size.width).ceil() as u32; - let rasterized_descent = (-bounds.origin.y).ceil() as i32; - let rasterized_ascent = (bounds.size.height + bounds.origin.y).ceil() as i32; - let rasterized_height = (rasterized_descent + rasterized_ascent) as u32; - - GlyphMetrics { - rasterized_ascent: rasterized_ascent, - rasterized_descent: rasterized_descent, - rasterized_left: rasterized_left, - rasterized_width: rasterized_width, - rasterized_height: rasterized_height, - } + let x_offset :f64 = SubpixelOffset::into(x_subpixel); + let y_offset :f64 = SubpixelOffset::into(y_subpixel); + + // First round out to pixel boundaries + // CG Origin is bottom left + let mut left = bounds.origin.x.floor() as i32; + let mut bottom = bounds.origin.y.floor() as i32; + let mut right = (bounds.origin.x + + bounds.size.width + + x_offset).ceil() as i32; + let mut top = (bounds.origin.y + + bounds.size.height + + y_offset).ceil() as i32; + + // Expand the bounds by 1 pixel, to give CG room for anti-aliasing. + // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset + // is not currently known, as CG dilates the outlines by some percentage. + // This is taken from Skia. + left -= 1; + bottom -= 1; + right += 1; + top += 1; + + let width = right - left; + let height = top - bottom; + + let metrics = GlyphMetrics { + rasterized_left: left, + rasterized_width: width as u32, + rasterized_height: height as u32, + rasterized_ascent: top, + rasterized_descent: -bottom, + }; + + metrics } impl FontContext { @@ -141,10 +166,12 @@ impl FontContext { pub fn get_glyph_dimensions(&mut self, font_key: FontKey, size: Au, - character: u32) -> Option { + character: u32, + x_subpixel: SubpixelOffset, + y_subpixel: SubpixelOffset) -> Option { self.get_ct_font(font_key, size).and_then(|ref ct_font| { let glyph = character as CGGlyph; - let metrics = get_glyph_metrics(ct_font, glyph); + let metrics = get_glyph_metrics(ct_font, glyph, x_subpixel, y_subpixel); if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 { None } else { @@ -161,6 +188,7 @@ impl FontContext { #[allow(dead_code)] fn print_glyph_data(&mut self, data: &Vec, width: usize, height: usize) { // Rust doesn't have step_by support on stable :( + println!("Width: {:?} height: {:?}", width, height); for i in 0..height { let current_height = i * width * 4; @@ -181,11 +209,13 @@ impl FontContext { color: ColorU, character: u32, render_mode: FontRenderMode, + x_suboffset: SubpixelOffset, + y_suboffset: SubpixelOffset, glyph_options: Option) -> Option { match self.get_ct_font(font_key, size) { Some(ref ct_font) => { let glyph = character as CGGlyph; - let metrics = get_glyph_metrics(ct_font, glyph); + let metrics = get_glyph_metrics(ct_font, glyph, x_suboffset, y_suboffset); if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 { return Some(RasterizedGlyph::blank()) } @@ -234,14 +264,22 @@ impl FontContext { cg_context.set_allows_font_subpixel_positioning(true); cg_context.set_should_subpixel_position_fonts(true); + // Don't quantize because we're doing it already. + cg_context.set_allows_font_subpixel_quantization(false); + cg_context.set_should_subpixel_quantize_fonts(false); + cg_context.set_allows_font_smoothing(smooth); cg_context.set_should_smooth_fonts(smooth); cg_context.set_allows_antialiasing(antialias); cg_context.set_should_antialias(antialias); + let x_offset :f64 = SubpixelOffset::into(x_suboffset); + let y_offset :f64 = SubpixelOffset::into(y_suboffset); + + // CG Origin is bottom left, WR is top left. Need -y offset let rasterization_origin = CGPoint { - x: -metrics.rasterized_left as f64, - y: metrics.rasterized_descent as f64, + x: -metrics.rasterized_left as f64 + x_offset, + y: metrics.rasterized_descent as f64 - y_offset, }; // Always draw black text on a white background diff --git a/webrender/src/platform/unix/font.rs b/webrender/src/platform/unix/font.rs index ba3f3c3701..5d50acfc0f 100644 --- a/webrender/src/platform/unix/font.rs +++ b/webrender/src/platform/unix/font.rs @@ -114,7 +114,9 @@ impl FontContext { pub fn get_glyph_dimensions(&self, font_key: FontKey, size: Au, - character: u32) -> Option { + character: u32, + x_subpixel: SubpixelOffset, + y_subpixel: SubpixelOffset) -> Option { self.load_glyph(font_key, size, character).and_then(|slot| { let metrics = unsafe { &(*slot).metrics }; if metrics.width == 0 || metrics.height == 0 { @@ -136,6 +138,8 @@ impl FontContext { color: ColorU, character: u32, render_mode: FontRenderMode, + x_suboffset: SubpixelOffset, + y_suboffset: SubpixelOffset, glyph_options: Option) -> Option { let mut glyph = None; diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index 65355b1b13..5ed4d3b3ac 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -395,7 +395,9 @@ impl ResourceCache { dimensions = font_context.get_glyph_dimensions(glyph_key.font_key, glyph_key.size, - glyph_key.index); + glyph_key.index, + glyph_key.x_suboffset, + glyph_key.y_suboffset); }); *entry.insert(dimensions) @@ -673,7 +675,6 @@ fn spawn_glyph_cache_thread() -> (Sender, Receiver (Sender, Receiver for SubpixelOffset { + fn into(self) -> f64 { + match self { + SubpixelOffset::Zero => 0.0, + SubpixelOffset::Quarter => 0.25, + SubpixelOffset::Half => 0.5, + SubpixelOffset::ThreeQuarters => 0.75, + } + } +} + #[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)] pub struct GlyphKey { pub font_key: FontKey,