tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }