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