Skip to main content

webrender/
transform.rs

1use api::units::{LayoutToPictureTransform, PicturePixel, PictureToLayoutTransform};
2use crate::{FastHashMap, frame_allocator::FrameMemory, gpu_types::VECS_PER_TRANSFORM};
3use crate::internal_types::FrameVec;
4use crate::spatial_tree::{SpatialNodeIndex, SpatialTree};
5use crate::util::{TransformedRectKind, MatrixHelpers};
6
7/* This Source Code Form is subject to the terms of the Mozilla Public
8 * License, v. 2.0. If a copy of the MPL was not distributed with this
9 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
10
11/// Represents the information about a transform palette
12/// entry that is passed to shaders. It includes an index
13/// into the transform palette, and a set of flags.
14#[derive(Copy, Clone, PartialEq, MallocSizeOf)]
15#[cfg_attr(feature = "capture", derive(Serialize))]
16#[cfg_attr(feature = "replay", derive(Deserialize))]
17#[repr(C)]
18pub struct GpuTransformId(pub u32);
19
20impl GpuTransformId {
21    /// Identity transform ID.
22    pub const IDENTITY: Self = GpuTransformId(0);
23    const INDEX_MASK: u32 = 0x003fffff;
24
25    // Note: we use unset bits instead of set bits to denote certain
26    // properties of the transform so that the identity transform id
27    // remains zero.
28
29    /// if *not* set, the transform is axis-aligned.
30    const AXIS_ALIGNED_2D_BIT: u32 = 1 << 23;
31    /// If *not* set, the transform can be represented as a 2d scale + offset.
32    const SCALE_OFFSET_2D_BIT: u32 = 1 << 22;
33
34    /// Extract the transform kind from the id.
35    pub fn transform_kind(&self) -> TransformedRectKind {
36        if (self.0 & Self::AXIS_ALIGNED_2D_BIT) == 0 {
37            TransformedRectKind::AxisAligned
38        } else {
39            TransformedRectKind::Complex
40        }
41    }
42
43    /// Note: There are transformations that preserve axis-alignment without
44    /// being scale + offsets.
45    pub fn is_2d_axis_aligned(&self) -> bool {
46        self.0 & Self::AXIS_ALIGNED_2D_BIT == 0
47    }
48
49    /// Returns true if the transform can be represented by a 2d scale + offset.
50    pub fn is_2d_scale_offset(&self) -> bool {
51        self.0 & Self::SCALE_OFFSET_2D_BIT == 0
52    }
53
54    pub fn metadata(&self) -> TransformMetadata {
55        TransformMetadata {
56            is_2d_axis_aligned: self.is_2d_axis_aligned(),
57            is_2d_scale_offset: self.is_2d_scale_offset(),
58        }
59    }
60
61    /// Override the kind of transform stored in this id. This can be useful in
62    /// cases where we don't want shaders to consider certain transforms axis-
63    /// aligned (i.e. perspective warp) even though we may still want to for the
64    /// general case.
65    pub fn override_transform_kind(&self, kind: TransformedRectKind) -> Self {
66        GpuTransformId((self.0 & (1 << 23)) | ((kind as u32) << 23))
67    }
68}
69
70impl std::fmt::Debug for GpuTransformId {
71    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
72        if *self == Self::IDENTITY {
73            write!(f, "<identity>")
74        } else {
75            let index = self.0 & Self::INDEX_MASK;
76            write!(f, "#{index}")?;
77            let flag_bits = Self::AXIS_ALIGNED_2D_BIT | Self::SCALE_OFFSET_2D_BIT;
78            if self.0 & flag_bits != flag_bits {
79                let axis_aligned = if self.is_2d_axis_aligned() { "axis-aligned" } else { "" };
80                let scale_offset = if self.is_2d_scale_offset() { "scale-offset" } else { "" };
81                write!(f, "({axis_aligned} {scale_offset})")?;
82            }
83            Ok(())
84        }
85    }
86}
87
88
89/// The GPU data payload for a transform palette entry.
90#[derive(Debug, Clone)]
91#[cfg_attr(feature = "capture", derive(Serialize))]
92#[cfg_attr(feature = "replay", derive(Deserialize))]
93#[repr(C)]
94pub struct TransformData {
95    transform: LayoutToPictureTransform,
96    inv_transform: PictureToLayoutTransform,
97}
98
99impl TransformData {
100    fn invalid() -> Self {
101        TransformData {
102            transform: LayoutToPictureTransform::identity(),
103            inv_transform: PictureToLayoutTransform::identity(),
104        }
105    }
106}
107
108// Extra data stored about each transform palette entry.
109#[derive(Copy, Clone)]
110pub struct TransformMetadata {
111    pub is_2d_axis_aligned: bool,
112    pub is_2d_scale_offset: bool,
113}
114
115impl TransformMetadata {
116    pub fn invalid() -> Self {
117        TransformMetadata {
118            is_2d_axis_aligned: true,
119            is_2d_scale_offset: true,
120        }
121    }
122
123    pub fn flags(&self) -> u32 {
124        let mut flags = 0;
125        if !self.is_2d_axis_aligned {
126            flags |= GpuTransformId::AXIS_ALIGNED_2D_BIT
127        };
128        if !self.is_2d_scale_offset {
129            flags |= GpuTransformId::SCALE_OFFSET_2D_BIT
130        };
131
132        flags
133    }
134}
135
136#[derive(Debug, Hash, Eq, PartialEq)]
137struct RelativeTransformKey {
138    from_index: SpatialNodeIndex,
139    to_index: SpatialNodeIndex,
140    scale: u32,
141    pre_scale: bool,
142}
143
144pub struct TransformPalette {
145    pub gpu: GpuTransforms,
146}
147
148impl TransformPalette {
149    pub fn new(
150        count: usize,
151        memory: &FrameMemory,
152    ) -> Self {
153        TransformPalette {
154            gpu: GpuTransforms::new(count, memory),
155        }
156    }
157
158    pub fn finish(self) -> FrameVec<TransformData> {
159        self.gpu.finish()
160    }
161}
162
163// Stores a contiguous list of TransformData structs, that
164// are ready for upload to the GPU.
165// TODO(gw): For now, this only stores the complete local
166//           to world transform for each spatial node. In
167//           the future, the transform palette will support
168//           specifying a coordinate system that the transform
169//           should be relative to.
170pub struct GpuTransforms {
171    transforms: FrameVec<TransformData>,
172    metadata: Vec<TransformMetadata>,
173    map: FastHashMap<RelativeTransformKey, usize>,
174}
175
176impl GpuTransforms {
177    fn new(
178        count: usize,
179        memory: &FrameMemory,
180    ) -> Self {
181        let _ = VECS_PER_TRANSFORM;
182
183        let mut transforms = memory.new_vec_with_capacity(count);
184        let mut metadata = Vec::with_capacity(count);
185
186        transforms.push(TransformData::invalid());
187        metadata.push(TransformMetadata::invalid());
188
189        GpuTransforms {
190            transforms,
191            metadata,
192            map: FastHashMap::default(),
193        }
194    }
195
196    fn finish(self) -> FrameVec<TransformData> {
197        self.transforms
198    }
199
200    fn get_index(
201        &mut self,
202        child_index: SpatialNodeIndex,
203        parent_index: SpatialNodeIndex,
204        mut scale: Option<f32>,
205        pre_scale: bool,
206        spatial_tree: &SpatialTree,
207    ) -> usize {
208        if scale == Some(1.0) {
209            scale = None;
210        }
211
212        // Deduplicate the common case of identity transforms.
213        if child_index == parent_index && scale.is_none() {
214            return 0;
215        }
216
217        let scale_key = scale.map(|s| s.to_bits()).unwrap_or(0);
218
219        let key = RelativeTransformKey {
220            from_index: child_index,
221            to_index: parent_index,
222            scale: scale_key,
223            pre_scale,
224        };
225
226        let metadata = &mut self.metadata;
227        let transforms = &mut self.transforms;
228
229        *self.map.entry(key).or_insert_with(|| {
230            let transform = spatial_tree.get_relative_transform(
231                child_index,
232                parent_index,
233            );
234
235            let is_2d_axis_aligned = transform.is_2d_axis_aligned();
236            let is_2d_scale_offset  = transform.is_2d_scale_translation();
237
238            let transform = transform
239                .into_transform()
240                .with_destination::<PicturePixel>();
241
242            register_gpu_transform(
243                metadata,
244                transforms,
245                transform,
246                scale,
247                pre_scale,
248                TransformMetadata {
249                    is_2d_axis_aligned,
250                    is_2d_scale_offset,
251                }
252            )
253        })
254    }
255
256    // Get a transform palette id for the given spatial node.
257    // TODO(gw): In the future, it will be possible to specify
258    //           a coordinate system id here, to allow retrieving
259    //           transforms in the local space of a given spatial node.
260    pub fn get_id(
261        &mut self,
262        from_index: SpatialNodeIndex,
263        to_index: SpatialNodeIndex,
264        spatial_tree: &SpatialTree,
265    ) -> GpuTransformId {
266        let index = self.get_index(
267            from_index,
268            to_index,
269            None,
270            false,
271            spatial_tree,
272        );
273
274        let flags = self.metadata[index].flags();
275
276        GpuTransformId((index as u32) | flags)
277    }
278
279    pub fn get_id_with_post_scale(
280        &mut self,
281        from_index: SpatialNodeIndex,
282        to_index: SpatialNodeIndex,
283        scale: f32,
284        spatial_tree: &SpatialTree,
285    ) -> GpuTransformId {
286        let index = self.get_index(
287            from_index,
288            to_index,
289            Some(scale),
290            false,
291            spatial_tree,
292        );
293
294        let flags = self.metadata[index].flags();
295
296        GpuTransformId((index as u32) | flags)
297    }
298
299    pub fn get_id_with_pre_scale(
300        &mut self,
301        scale: f32,
302        from_index: SpatialNodeIndex,
303        to_index: SpatialNodeIndex,
304        spatial_tree: &SpatialTree,
305    ) -> GpuTransformId {
306        let index = self.get_index(
307            from_index,
308            to_index,
309            Some(scale),
310            true,
311            spatial_tree,
312        );
313
314        let flags = self.metadata[index].flags();
315
316        GpuTransformId((index as u32) | flags)
317    }
318
319    pub fn get_custom(
320        &mut self,
321        transform: LayoutToPictureTransform,
322    ) -> GpuTransformId {
323        let is_2d_scale_offset = transform.is_2d_scale_translation();
324        let is_axis_aligned = transform.preserves_2d_axis_alignment();
325        let metadata = TransformMetadata {
326            is_2d_scale_offset,
327            is_2d_axis_aligned: is_axis_aligned,
328        };
329        let index = register_gpu_transform(
330            &mut self.metadata,
331            &mut self.transforms,
332            transform,
333            None,
334            false,
335            metadata,
336        );
337
338        GpuTransformId((index as u32) | metadata.flags())
339    }
340}
341
342// Set the local -> world transform for a given spatial
343// node in the transform palette.
344fn register_gpu_transform(
345    metadatas: &mut Vec<TransformMetadata>,
346    transforms: &mut FrameVec<TransformData>,
347    mut transform: LayoutToPictureTransform,
348    scale: Option<f32>,
349    pre_scale: bool,
350    metadata: TransformMetadata,
351) -> usize {
352    if let Some(scale) = scale {
353        if pre_scale {
354            transform = transform.pre_scale(scale, scale, 1.0);
355        } else {
356            transform = transform.then_scale(scale, scale, 1.0);
357        }
358    }
359    // TODO: refactor the calling code to not even try
360    // registering a non-invertible transform.
361    let inv_transform = transform
362        .inverse()
363        .unwrap_or_else(PictureToLayoutTransform::identity);
364
365    let data = TransformData {
366        transform,
367        inv_transform,
368    };
369
370    let index = transforms.len();
371    metadatas.push(metadata);
372    transforms.push(data);
373
374    index
375}