webrender/prim_store/
text_run.rs

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