text_run.rs (20720B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use api::{ColorF, FontInstanceFlags, GlyphInstance, RasterSpace, Shadow, GlyphIndex}; 6 use api::units::{LayoutToWorldTransform, LayoutVector2D, RasterPixelScale, DevicePixelScale}; 7 use api::units::*; 8 use crate::scene_building::{CreateShadow, IsVisible}; 9 use crate::frame_builder::FrameBuildingState; 10 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT}; 11 use crate::intern; 12 use crate::internal_types::LayoutPrimitiveInfo; 13 use crate::picture::SurfaceInfo; 14 use crate::prim_store::{PrimitiveOpacity, PrimitiveScratchBuffer}; 15 use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonData}; 16 use crate::renderer::{GpuBufferBuilderF, MAX_VERTEX_TEXTURE_WIDTH}; 17 use crate::resource_cache::ResourceCache; 18 use crate::util::MatrixHelpers; 19 use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind, LayoutPointAu}; 20 use crate::spatial_tree::{SpatialTree, SpatialNodeIndex}; 21 use crate::space::SpaceSnapper; 22 23 use std::ops; 24 25 use super::{storage, VectorKey}; 26 27 #[cfg_attr(feature = "capture", derive(Serialize))] 28 #[cfg_attr(feature = "replay", derive(Deserialize))] 29 #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] 30 pub struct GlyphInstanceAu { 31 pub index: GlyphIndex, 32 pub point: LayoutPointAu, 33 } 34 35 /// A run of glyphs, with associated font information. 36 #[cfg_attr(feature = "capture", derive(Serialize))] 37 #[cfg_attr(feature = "replay", derive(Deserialize))] 38 #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] 39 pub struct TextRunKey { 40 pub common: PrimKeyCommonData, 41 pub font: FontInstance, 42 pub glyphs: Vec<GlyphInstanceAu>, 43 pub shadow: bool, 44 pub requested_raster_space: RasterSpace, 45 pub reference_frame_offset: VectorKey, 46 } 47 48 impl TextRunKey { 49 pub fn new( 50 info: &LayoutPrimitiveInfo, 51 text_run: TextRun, 52 ) -> Self { 53 let glyphs = text_run 54 .glyphs 55 .iter() 56 .map(|glyph| { 57 GlyphInstanceAu { 58 index: glyph.index, 59 point: glyph.point.to_au(), 60 } 61 }) 62 .collect(); 63 64 TextRunKey { 65 common: info.into(), 66 font: text_run.font, 67 glyphs, 68 shadow: text_run.shadow, 69 requested_raster_space: text_run.requested_raster_space, 70 reference_frame_offset: text_run.reference_frame_offset.into(), 71 } 72 } 73 } 74 75 impl intern::InternDebug for TextRunKey {} 76 77 #[cfg_attr(feature = "capture", derive(Serialize))] 78 #[cfg_attr(feature = "replay", derive(Deserialize))] 79 #[derive(MallocSizeOf)] 80 pub struct TextRunTemplate { 81 pub common: PrimTemplateCommonData, 82 pub font: FontInstance, 83 pub glyphs: Vec<GlyphInstance>, 84 } 85 86 impl ops::Deref for TextRunTemplate { 87 type Target = PrimTemplateCommonData; 88 fn deref(&self) -> &Self::Target { 89 &self.common 90 } 91 } 92 93 impl ops::DerefMut for TextRunTemplate { 94 fn deref_mut(&mut self) -> &mut Self::Target { 95 &mut self.common 96 } 97 } 98 99 impl From<TextRunKey> for TextRunTemplate { 100 fn from(item: TextRunKey) -> Self { 101 let common = PrimTemplateCommonData::with_key_common(item.common); 102 let glyphs = item 103 .glyphs 104 .iter() 105 .map(|glyph| { 106 GlyphInstance { 107 index: glyph.index, 108 point: LayoutPoint::from_au(glyph.point), 109 } 110 }) 111 .collect(); 112 113 TextRunTemplate { 114 common, 115 font: item.font, 116 glyphs, 117 } 118 } 119 } 120 121 impl TextRunTemplate { 122 /// Update the GPU cache for a given primitive template. This may be called multiple 123 /// times per frame, by each primitive reference that refers to this interned 124 /// template. The initial request call to the GPU cache ensures that work is only 125 /// done if the cache entry is invalid (due to first use or eviction). 126 pub fn update( 127 &mut self, 128 frame_state: &mut FrameBuildingState, 129 ) { 130 self.write_prim_gpu_blocks(frame_state); 131 self.opacity = PrimitiveOpacity::translucent(); 132 } 133 134 fn write_prim_gpu_blocks( 135 &mut self, 136 frame_state: &mut FrameBuildingState, 137 ) { 138 // Corresponds to `fetch_glyph` in the shaders. 139 let num_blocks = (self.glyphs.len() + 1) / 2 + 1; 140 assert!(num_blocks <= MAX_VERTEX_TEXTURE_WIDTH); 141 let mut writer = frame_state.frame_gpu_data.f32.write_blocks(num_blocks); 142 writer.push_one(ColorF::from(self.font.color).premultiplied()); 143 144 let mut gpu_block = [0.0; 4]; 145 for (i, src) in self.glyphs.iter().enumerate() { 146 // Two glyphs are packed per GPU block. 147 if (i & 1) == 0 { 148 gpu_block[0] = src.point.x; 149 gpu_block[1] = src.point.y; 150 } else { 151 gpu_block[2] = src.point.x; 152 gpu_block[3] = src.point.y; 153 writer.push_one(gpu_block); 154 } 155 } 156 157 // Ensure the last block is added in the case 158 // of an odd number of glyphs. 159 if (self.glyphs.len() & 1) != 0 { 160 writer.push_one(gpu_block); 161 } 162 163 self.common.gpu_buffer_address = writer.finish(); 164 } 165 } 166 167 pub type TextRunDataHandle = intern::Handle<TextRun>; 168 169 #[derive(Debug, MallocSizeOf)] 170 #[cfg_attr(feature = "capture", derive(Serialize))] 171 #[cfg_attr(feature = "replay", derive(Deserialize))] 172 pub struct TextRun { 173 pub font: FontInstance, 174 pub glyphs: Vec<GlyphInstance>, 175 pub shadow: bool, 176 pub requested_raster_space: RasterSpace, 177 pub reference_frame_offset: LayoutVector2D, 178 } 179 180 impl intern::Internable for TextRun { 181 type Key = TextRunKey; 182 type StoreData = TextRunTemplate; 183 type InternData = (); 184 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_TEXT_RUNS; 185 } 186 187 impl InternablePrimitive for TextRun { 188 fn into_key( 189 self, 190 info: &LayoutPrimitiveInfo, 191 ) -> TextRunKey { 192 TextRunKey::new( 193 info, 194 self, 195 ) 196 } 197 198 fn make_instance_kind( 199 key: TextRunKey, 200 data_handle: TextRunDataHandle, 201 prim_store: &mut PrimitiveStore, 202 ) -> PrimitiveInstanceKind { 203 let reference_frame_offset = key.reference_frame_offset.into(); 204 205 let run_index = prim_store.text_runs.push(TextRunPrimitive { 206 used_font: key.font.clone(), 207 glyph_keys_range: storage::Range::empty(), 208 reference_frame_relative_offset: reference_frame_offset, 209 snapped_reference_frame_relative_offset: reference_frame_offset, 210 shadow: key.shadow, 211 raster_scale: 1.0, 212 requested_raster_space: key.requested_raster_space, 213 }); 214 215 PrimitiveInstanceKind::TextRun{ data_handle, run_index } 216 } 217 } 218 219 impl CreateShadow for TextRun { 220 fn create_shadow( 221 &self, 222 shadow: &Shadow, 223 blur_is_noop: bool, 224 current_raster_space: RasterSpace, 225 ) -> Self { 226 let mut font = FontInstance { 227 color: shadow.color.into(), 228 ..self.font.clone() 229 }; 230 if shadow.blur_radius > 0.0 { 231 font.disable_subpixel_aa(); 232 } 233 234 let requested_raster_space = if blur_is_noop { 235 current_raster_space 236 } else { 237 RasterSpace::Local(1.0) 238 }; 239 240 TextRun { 241 font, 242 glyphs: self.glyphs.clone(), 243 shadow: true, 244 requested_raster_space, 245 reference_frame_offset: self.reference_frame_offset, 246 } 247 } 248 } 249 250 impl IsVisible for TextRun { 251 fn is_visible(&self) -> bool { 252 self.font.color.a > 0 253 } 254 } 255 256 #[derive(Debug)] 257 #[cfg_attr(feature = "capture", derive(Serialize))] 258 pub struct TextRunPrimitive { 259 pub used_font: FontInstance, 260 pub glyph_keys_range: storage::Range<GlyphKey>, 261 pub reference_frame_relative_offset: LayoutVector2D, 262 pub snapped_reference_frame_relative_offset: LayoutVector2D, 263 pub shadow: bool, 264 pub raster_scale: f32, 265 pub requested_raster_space: RasterSpace, 266 } 267 268 impl TextRunPrimitive { 269 pub fn update_font_instance( 270 &mut self, 271 specified_font: &FontInstance, 272 surface: &SurfaceInfo, 273 spatial_node_index: SpatialNodeIndex, 274 transform: &LayoutToWorldTransform, 275 allow_subpixel: bool, 276 raster_space: RasterSpace, 277 spatial_tree: &SpatialTree, 278 ) -> bool { 279 // If local raster space is specified, include that in the scale 280 // of the glyphs that get rasterized. 281 // TODO(gw): Once we support proper local space raster modes, this 282 // will implicitly be part of the device pixel ratio for 283 // the (cached) local space surface, and so this code 284 // will no longer be required. 285 let raster_scale = raster_space.local_scale().unwrap_or(1.0).max(0.001); 286 287 let dps = surface.device_pixel_scale.0; 288 let font_size = specified_font.size.to_f32_px(); 289 290 // Small floating point error can accumulate in the raster * device_pixel scale. 291 // Round that to the nearest 100th of a scale factor to remove this error while 292 // still allowing reasonably accurate scale factors when a pinch-zoom is stopped 293 // at a fractional amount. 294 let quantized_scale = (dps * raster_scale * 100.0).round() / 100.0; 295 let mut device_font_size = font_size * quantized_scale; 296 297 // Check there is a valid transform that doesn't exceed the font size limit. 298 // Ensure the font is supposed to be rasterized in screen-space. 299 // Only support transforms that can be coerced to simple 2D transforms. 300 // Add texture padding to the rasterized glyph buffer when one anticipates 301 // the glyph will need to be scaled when rendered. 302 let (use_subpixel_aa, transform_glyphs, texture_padding, oversized) = if raster_space != RasterSpace::Screen || 303 transform.has_perspective_component() || !transform.has_2d_inverse() 304 { 305 (false, false, true, device_font_size > FONT_SIZE_LIMIT) 306 } else if transform.exceeds_2d_scale((FONT_SIZE_LIMIT / device_font_size) as f64) { 307 (false, false, true, true) 308 } else { 309 (true, !transform.is_simple_2d_translation(), false, false) 310 }; 311 312 let font_transform = if transform_glyphs { 313 // Get the font transform matrix (skew / scale) from the complete transform. 314 // Fold in the device pixel scale. 315 self.raster_scale = 1.0; 316 FontTransform::from(transform) 317 } else { 318 if oversized { 319 // Font sizes larger than the limit need to be scaled, thus can't use subpixels. 320 // In this case we adjust the font size and raster space to ensure 321 // we rasterize at the limit, to minimize the amount of scaling. 322 let limited_raster_scale = FONT_SIZE_LIMIT / (font_size * dps); 323 device_font_size = FONT_SIZE_LIMIT; 324 325 // Record the raster space the text needs to be snapped in. The original raster 326 // scale would have been too big. 327 self.raster_scale = limited_raster_scale; 328 } else { 329 // Record the raster space the text needs to be snapped in. We may have changed 330 // from RasterSpace::Screen due to a transform with perspective or without a 2d 331 // inverse, or it may have been RasterSpace::Local all along. 332 self.raster_scale = raster_scale; 333 } 334 335 // Rasterize the glyph without any transform 336 FontTransform::identity() 337 }; 338 339 // TODO(aosmond): Snapping really ought to happen during scene building 340 // as much as possible. This will allow clips to be already adjusted 341 // based on the snapping requirements of the primitive. This may affect 342 // complex clips that create a different task, and when we rasterize 343 // glyphs without the transform (because the shader doesn't have the 344 // snap offsets to adjust its clip). These rects are fairly conservative 345 // to begin with and do not appear to be causing significant issues at 346 // this time. 347 self.snapped_reference_frame_relative_offset = if transform_glyphs { 348 // Don't touch the reference frame relative offset. We'll let the 349 // shader do the snapping in device pixels. 350 self.reference_frame_relative_offset 351 } else { 352 // TODO(dp): The SurfaceInfo struct needs to be updated to use RasterPixelScale 353 // rather than DevicePixelScale, however this is a large chunk of 354 // work that will be done as a follow up patch. 355 let raster_pixel_scale = RasterPixelScale::new(surface.device_pixel_scale.0); 356 357 // There may be an animation, so snap the reference frame relative 358 // offset such that it excludes the impact, if any. 359 let snap_to_device = SpaceSnapper::new_with_target( 360 surface.raster_spatial_node_index, 361 spatial_node_index, 362 raster_pixel_scale, 363 spatial_tree, 364 ); 365 snap_to_device.snap_point(&self.reference_frame_relative_offset.to_point()).to_vector() 366 }; 367 368 let mut flags = specified_font.flags; 369 if transform_glyphs { 370 flags |= FontInstanceFlags::TRANSFORM_GLYPHS; 371 } 372 if texture_padding { 373 flags |= FontInstanceFlags::TEXTURE_PADDING; 374 } 375 376 // If the transform or device size is different, then the caller of 377 // this method needs to know to rebuild the glyphs. 378 let cache_dirty = 379 self.used_font.transform != font_transform || 380 self.used_font.size != device_font_size.into() || 381 self.used_font.flags != flags; 382 383 // Construct used font instance from the specified font instance 384 self.used_font = FontInstance { 385 transform: font_transform, 386 size: device_font_size.into(), 387 flags, 388 ..specified_font.clone() 389 }; 390 391 // If using local space glyphs, we don't want subpixel AA. 392 if !allow_subpixel || !use_subpixel_aa { 393 self.used_font.disable_subpixel_aa(); 394 395 // Disable subpixel positioning for oversized glyphs to avoid 396 // thrashing the glyph cache with many subpixel variations of 397 // big glyph textures. A possible subpixel positioning error 398 // is small relative to the maximum font size and thus should 399 // not be very noticeable. 400 if oversized { 401 self.used_font.disable_subpixel_position(); 402 } 403 } 404 405 cache_dirty 406 } 407 408 /// Gets the raster space to use when rendering this primitive. 409 /// Usually this would be the requested raster space. However, if 410 /// the primitive's spatial node or one of its ancestors is being pinch zoomed 411 /// then we round it. This prevents us rasterizing glyphs for every minor 412 /// change in zoom level, as that would be too expensive. 413 fn get_raster_space_for_prim( 414 &self, 415 prim_spatial_node_index: SpatialNodeIndex, 416 low_quality_pinch_zoom: bool, 417 device_pixel_scale: DevicePixelScale, 418 spatial_tree: &SpatialTree, 419 ) -> RasterSpace { 420 let prim_spatial_node = spatial_tree.get_spatial_node(prim_spatial_node_index); 421 if prim_spatial_node.is_ancestor_or_self_zooming { 422 if low_quality_pinch_zoom { 423 // In low-quality mode, we set the scale to be 1.0. However, the device-pixel 424 // scale selected for the zoom will be taken into account in the caller to this 425 // function when it's converted from local -> device pixels. Since in this mode 426 // the device-pixel scale is constant during the zoom, this gives the desired 427 // performance while also allowing the scale to be adjusted to a new factor at 428 // the end of a pinch-zoom. 429 RasterSpace::Local(1.0) 430 } else { 431 let root_spatial_node_index = spatial_tree.root_reference_frame_index(); 432 433 // For high-quality mode, we quantize the exact scale factor as before. However, 434 // we want to _undo_ the effect of the device-pixel scale on the picture cache 435 // tiles (which changes now that they are raster roots). Divide the rounded value 436 // by the device-pixel scale so that the local -> device conversion has no effect. 437 let scale_factors = spatial_tree 438 .get_relative_transform(prim_spatial_node_index, root_spatial_node_index) 439 .scale_factors(); 440 441 // Round the scale up to the nearest power of 2, but don't exceed 8. 442 let scale = scale_factors.0.max(scale_factors.1).min(8.0).max(1.0); 443 let rounded_up = 2.0f32.powf(scale.log2().ceil()); 444 445 RasterSpace::Local(rounded_up / device_pixel_scale.0) 446 } 447 } else { 448 // Assume that if we have a RasterSpace::Local, it is frequently changing, in which 449 // case we want to undo the device-pixel scale, as we do above. 450 match self.requested_raster_space { 451 RasterSpace::Local(scale) => RasterSpace::Local(scale / device_pixel_scale.0), 452 RasterSpace::Screen => RasterSpace::Screen, 453 } 454 } 455 } 456 457 pub fn request_resources( 458 &mut self, 459 prim_offset: LayoutVector2D, 460 specified_font: &FontInstance, 461 glyphs: &[GlyphInstance], 462 transform: &LayoutToWorldTransform, 463 surface: &SurfaceInfo, 464 spatial_node_index: SpatialNodeIndex, 465 allow_subpixel: bool, 466 low_quality_pinch_zoom: bool, 467 resource_cache: &mut ResourceCache, 468 gpu_buffer: &mut GpuBufferBuilderF, 469 spatial_tree: &SpatialTree, 470 scratch: &mut PrimitiveScratchBuffer, 471 ) { 472 let raster_space = self.get_raster_space_for_prim( 473 spatial_node_index, 474 low_quality_pinch_zoom, 475 surface.device_pixel_scale, 476 spatial_tree, 477 ); 478 479 let cache_dirty = self.update_font_instance( 480 specified_font, 481 surface, 482 spatial_node_index, 483 transform, 484 allow_subpixel, 485 raster_space, 486 spatial_tree, 487 ); 488 489 if self.glyph_keys_range.is_empty() || cache_dirty { 490 let subpx_dir = self.used_font.get_subpx_dir(); 491 492 let dps = surface.device_pixel_scale.0; 493 let transform = match raster_space { 494 RasterSpace::Local(scale) => FontTransform::new(scale * dps, 0.0, 0.0, scale * dps), 495 RasterSpace::Screen => self.used_font.transform.scale(dps), 496 }; 497 498 self.glyph_keys_range = scratch.glyph_keys.extend( 499 glyphs.iter().map(|src| { 500 let src_point = src.point + prim_offset; 501 let device_offset = transform.transform(&src_point); 502 GlyphKey::new(src.index, device_offset, subpx_dir) 503 })); 504 } 505 506 resource_cache.request_glyphs( 507 self.used_font.clone(), 508 &scratch.glyph_keys[self.glyph_keys_range], 509 gpu_buffer, 510 ); 511 } 512 } 513 514 /// These are linux only because FontInstancePlatformOptions varies in size by platform. 515 #[test] 516 #[cfg(target_os = "linux")] 517 fn test_struct_sizes() { 518 use std::mem; 519 // The sizes of these structures are critical for performance on a number of 520 // talos stress tests. If you get a failure here on CI, there's two possibilities: 521 // (a) You made a structure smaller than it currently is. Great work! Update the 522 // test expectations and move on. 523 // (b) You made a structure larger. This is not necessarily a problem, but should only 524 // be done with care, and after checking if talos performance regresses badly. 525 assert_eq!(mem::size_of::<TextRun>(), 88, "TextRun size changed"); 526 assert_eq!(mem::size_of::<TextRunTemplate>(), 88, "TextRunTemplate size changed"); 527 assert_eq!(mem::size_of::<TextRunKey>(), 104, "TextRunKey size changed"); 528 assert_eq!(mem::size_of::<TextRunPrimitive>(), 80, "TextRunPrimitive size changed"); 529 }