Skip to main content

azul_core/
transform.rs

1//! 3D transform matrix computations for CSS transforms.
2//!
3//! This module implements 4x4 transformation matrices for CSS `transform` properties,
4//! including translation, rotation, scaling, skewing, and perspective. It handles conversion
5//! from CSS transform functions to hardware-accelerated matrices for WebRender.
6//!
7//! On x86_64 platforms, the module automatically detects and uses SSE/AVX instructions
8//! for optimized matrix multiplication and inversion.
9//!
10//! **NOTE**: Matrices are stored in **row-major** format (unlike some graphics APIs that
11//! use column-major). The module handles coordinate system differences between WebRender
12//! and hit-testing via the `RotationMode` enum.
13
14use core::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
15
16use azul_css::props::style::{StyleTransform, StyleTransformOrigin};
17
18use crate::geom::LogicalPosition;
19
20/// CPU feature detection: true if initialization has been performed
21pub static INITIALIZED: AtomicBool = AtomicBool::new(false);
22/// CPU feature detection: true if AVX instructions are available
23pub static USE_AVX: AtomicBool = AtomicBool::new(false);
24/// CPU feature detection: true if SSE instructions are available
25pub static USE_SSE: AtomicBool = AtomicBool::new(false);
26
27/// Specifies the coordinate system convention for rotations.
28///
29/// WebRender uses a different rotation direction than hit-testing, so transforms
30/// must be adjusted based on their use case. This enum controls whether the
31/// rotation matrix is inverted to match the expected behavior.
32#[derive(Debug, Copy, Clone)]
33pub enum RotationMode {
34    /// Use rotation convention for WebRender (counter-clockwise, requires inversion)
35    ForWebRender,
36    /// Use rotation convention for hit-testing (clockwise, no inversion)
37    ForHitTesting,
38}
39
40/// A computed 4x4 transformation matrix in pixel space.
41///
42/// Represents the final transformation matrix for a DOM element after applying
43/// all CSS transform functions (translate, rotate, scale, etc.) and accounting
44/// for transform-origin.
45///
46/// # Memory Layout
47///
48/// Matrix is stored in **row-major** format:
49/// ```text
50/// m[0] = [m11, m12, m13, m14]
51/// m[1] = [m21, m22, m23, m24]
52/// m[2] = [m31, m32, m33, m34]
53/// m[3] = [m41, m42, m43, m44]
54/// ```
55#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
56#[repr(C)]
57pub struct ComputedTransform3D {
58    /// The 4x4 matrix in row-major format
59    pub m: [[f32; 4]; 4],
60}
61
62impl ComputedTransform3D {
63    /// The identity matrix (no transformation).
64    pub const IDENTITY: Self = Self {
65        m: [
66            [1.0, 0.0, 0.0, 0.0],
67            [0.0, 1.0, 0.0, 0.0],
68            [0.0, 0.0, 1.0, 0.0],
69            [0.0, 0.0, 0.0, 1.0],
70        ],
71    };
72
73    /// Creates a new 4x4 transformation matrix with the given elements.
74    ///
75    /// Elements are specified in row-major order (m11, m12, ..., m44).
76    pub const fn new(
77        m11: f32,
78        m12: f32,
79        m13: f32,
80        m14: f32,
81        m21: f32,
82        m22: f32,
83        m23: f32,
84        m24: f32,
85        m31: f32,
86        m32: f32,
87        m33: f32,
88        m34: f32,
89        m41: f32,
90        m42: f32,
91        m43: f32,
92        m44: f32,
93    ) -> Self {
94        Self {
95            m: [
96                [m11, m12, m13, m14],
97                [m21, m22, m23, m24],
98                [m31, m32, m33, m34],
99                [m41, m42, m43, m44],
100            ],
101        }
102    }
103
104    /// Creates a 2D transformation matrix (3D matrix with Z = 0).
105    ///
106    /// This is equivalent to the CSS `matrix()` function. The transformation
107    /// only affects the X and Y axes.
108    ///
109    /// Corresponds to `matrix(m11, m12, m21, m22, m41, m42)` in CSS.
110    pub const fn new_2d(m11: f32, m12: f32, m21: f32, m22: f32, m41: f32, m42: f32) -> Self {
111        Self::new(
112            m11, m12, 0.0, 0.0, m21, m22, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, m41, m42, 0.0, 1.0,
113        )
114    }
115
116    /// Computes the inverse of this transformation matrix.
117    ///
118    /// This function uses a standard matrix inversion algorithm. Returns the
119    /// identity matrix if the determinant is zero (singular matrix).
120    ///
121    /// NOTE: This is a relatively expensive operation.
122    pub fn inverse(&self) -> Self {
123        let det = self.determinant();
124
125        // if det == 0.0 { return None; }
126
127        let m = ComputedTransform3D::new(
128            self.m[1][2] * self.m[2][3] * self.m[3][1] - self.m[1][3] * self.m[2][2] * self.m[3][1]
129                + self.m[1][3] * self.m[2][1] * self.m[3][2]
130                - self.m[1][1] * self.m[2][3] * self.m[3][2]
131                - self.m[1][2] * self.m[2][1] * self.m[3][3]
132                + self.m[1][1] * self.m[2][2] * self.m[3][3],
133            self.m[0][3] * self.m[2][2] * self.m[3][1]
134                - self.m[0][2] * self.m[2][3] * self.m[3][1]
135                - self.m[0][3] * self.m[2][1] * self.m[3][2]
136                + self.m[0][1] * self.m[2][3] * self.m[3][2]
137                + self.m[0][2] * self.m[2][1] * self.m[3][3]
138                - self.m[0][1] * self.m[2][2] * self.m[3][3],
139            self.m[0][2] * self.m[1][3] * self.m[3][1] - self.m[0][3] * self.m[1][2] * self.m[3][1]
140                + self.m[0][3] * self.m[1][1] * self.m[3][2]
141                - self.m[0][1] * self.m[1][3] * self.m[3][2]
142                - self.m[0][2] * self.m[1][1] * self.m[3][3]
143                + self.m[0][1] * self.m[1][2] * self.m[3][3],
144            self.m[0][3] * self.m[1][2] * self.m[2][1]
145                - self.m[0][2] * self.m[1][3] * self.m[2][1]
146                - self.m[0][3] * self.m[1][1] * self.m[2][2]
147                + self.m[0][1] * self.m[1][3] * self.m[2][2]
148                + self.m[0][2] * self.m[1][1] * self.m[2][3]
149                - self.m[0][1] * self.m[1][2] * self.m[2][3],
150            self.m[1][3] * self.m[2][2] * self.m[3][0]
151                - self.m[1][2] * self.m[2][3] * self.m[3][0]
152                - self.m[1][3] * self.m[2][0] * self.m[3][2]
153                + self.m[1][0] * self.m[2][3] * self.m[3][2]
154                + self.m[1][2] * self.m[2][0] * self.m[3][3]
155                - self.m[1][0] * self.m[2][2] * self.m[3][3],
156            self.m[0][2] * self.m[2][3] * self.m[3][0] - self.m[0][3] * self.m[2][2] * self.m[3][0]
157                + self.m[0][3] * self.m[2][0] * self.m[3][2]
158                - self.m[0][0] * self.m[2][3] * self.m[3][2]
159                - self.m[0][2] * self.m[2][0] * self.m[3][3]
160                + self.m[0][0] * self.m[2][2] * self.m[3][3],
161            self.m[0][3] * self.m[1][2] * self.m[3][0]
162                - self.m[0][2] * self.m[1][3] * self.m[3][0]
163                - self.m[0][3] * self.m[1][0] * self.m[3][2]
164                + self.m[0][0] * self.m[1][3] * self.m[3][2]
165                + self.m[0][2] * self.m[1][0] * self.m[3][3]
166                - self.m[0][0] * self.m[1][2] * self.m[3][3],
167            self.m[0][2] * self.m[1][3] * self.m[2][0] - self.m[0][3] * self.m[1][2] * self.m[2][0]
168                + self.m[0][3] * self.m[1][0] * self.m[2][2]
169                - self.m[0][0] * self.m[1][3] * self.m[2][2]
170                - self.m[0][2] * self.m[1][0] * self.m[2][3]
171                + self.m[0][0] * self.m[1][2] * self.m[2][3],
172            self.m[1][1] * self.m[2][3] * self.m[3][0] - self.m[1][3] * self.m[2][1] * self.m[3][0]
173                + self.m[1][3] * self.m[2][0] * self.m[3][1]
174                - self.m[1][0] * self.m[2][3] * self.m[3][1]
175                - self.m[1][1] * self.m[2][0] * self.m[3][3]
176                + self.m[1][0] * self.m[2][1] * self.m[3][3],
177            self.m[0][3] * self.m[2][1] * self.m[3][0]
178                - self.m[0][1] * self.m[2][3] * self.m[3][0]
179                - self.m[0][3] * self.m[2][0] * self.m[3][1]
180                + self.m[0][0] * self.m[2][3] * self.m[3][1]
181                + self.m[0][1] * self.m[2][0] * self.m[3][3]
182                - self.m[0][0] * self.m[2][1] * self.m[3][3],
183            self.m[0][1] * self.m[1][3] * self.m[3][0] - self.m[0][3] * self.m[1][1] * self.m[3][0]
184                + self.m[0][3] * self.m[1][0] * self.m[3][1]
185                - self.m[0][0] * self.m[1][3] * self.m[3][1]
186                - self.m[0][1] * self.m[1][0] * self.m[3][3]
187                + self.m[0][0] * self.m[1][1] * self.m[3][3],
188            self.m[0][3] * self.m[1][1] * self.m[2][0]
189                - self.m[0][1] * self.m[1][3] * self.m[2][0]
190                - self.m[0][3] * self.m[1][0] * self.m[2][1]
191                + self.m[0][0] * self.m[1][3] * self.m[2][1]
192                + self.m[0][1] * self.m[1][0] * self.m[2][3]
193                - self.m[0][0] * self.m[1][1] * self.m[2][3],
194            self.m[1][2] * self.m[2][1] * self.m[3][0]
195                - self.m[1][1] * self.m[2][2] * self.m[3][0]
196                - self.m[1][2] * self.m[2][0] * self.m[3][1]
197                + self.m[1][0] * self.m[2][2] * self.m[3][1]
198                + self.m[1][1] * self.m[2][0] * self.m[3][2]
199                - self.m[1][0] * self.m[2][1] * self.m[3][2],
200            self.m[0][1] * self.m[2][2] * self.m[3][0] - self.m[0][2] * self.m[2][1] * self.m[3][0]
201                + self.m[0][2] * self.m[2][0] * self.m[3][1]
202                - self.m[0][0] * self.m[2][2] * self.m[3][1]
203                - self.m[0][1] * self.m[2][0] * self.m[3][2]
204                + self.m[0][0] * self.m[2][1] * self.m[3][2],
205            self.m[0][2] * self.m[1][1] * self.m[3][0]
206                - self.m[0][1] * self.m[1][2] * self.m[3][0]
207                - self.m[0][2] * self.m[1][0] * self.m[3][1]
208                + self.m[0][0] * self.m[1][2] * self.m[3][1]
209                + self.m[0][1] * self.m[1][0] * self.m[3][2]
210                - self.m[0][0] * self.m[1][1] * self.m[3][2],
211            self.m[0][1] * self.m[1][2] * self.m[2][0] - self.m[0][2] * self.m[1][1] * self.m[2][0]
212                + self.m[0][2] * self.m[1][0] * self.m[2][1]
213                - self.m[0][0] * self.m[1][2] * self.m[2][1]
214                - self.m[0][1] * self.m[1][0] * self.m[2][2]
215                + self.m[0][0] * self.m[1][1] * self.m[2][2],
216        );
217
218        m.multiply_scalar(1.0 / det)
219    }
220
221    fn determinant(&self) -> f32 {
222        self.m[0][3] * self.m[1][2] * self.m[2][1] * self.m[3][0]
223            - self.m[0][2] * self.m[1][3] * self.m[2][1] * self.m[3][0]
224            - self.m[0][3] * self.m[1][1] * self.m[2][2] * self.m[3][0]
225            + self.m[0][1] * self.m[1][3] * self.m[2][2] * self.m[3][0]
226            + self.m[0][2] * self.m[1][1] * self.m[2][3] * self.m[3][0]
227            - self.m[0][1] * self.m[1][2] * self.m[2][3] * self.m[3][0]
228            - self.m[0][3] * self.m[1][2] * self.m[2][0] * self.m[3][1]
229            + self.m[0][2] * self.m[1][3] * self.m[2][0] * self.m[3][1]
230            + self.m[0][3] * self.m[1][0] * self.m[2][2] * self.m[3][1]
231            - self.m[0][0] * self.m[1][3] * self.m[2][2] * self.m[3][1]
232            - self.m[0][2] * self.m[1][0] * self.m[2][3] * self.m[3][1]
233            + self.m[0][0] * self.m[1][2] * self.m[2][3] * self.m[3][1]
234            + self.m[0][3] * self.m[1][1] * self.m[2][0] * self.m[3][2]
235            - self.m[0][1] * self.m[1][3] * self.m[2][0] * self.m[3][2]
236            - self.m[0][3] * self.m[1][0] * self.m[2][1] * self.m[3][2]
237            + self.m[0][0] * self.m[1][3] * self.m[2][1] * self.m[3][2]
238            + self.m[0][1] * self.m[1][0] * self.m[2][3] * self.m[3][2]
239            - self.m[0][0] * self.m[1][1] * self.m[2][3] * self.m[3][2]
240            - self.m[0][2] * self.m[1][1] * self.m[2][0] * self.m[3][3]
241            + self.m[0][1] * self.m[1][2] * self.m[2][0] * self.m[3][3]
242            + self.m[0][2] * self.m[1][0] * self.m[2][1] * self.m[3][3]
243            - self.m[0][0] * self.m[1][2] * self.m[2][1] * self.m[3][3]
244            - self.m[0][1] * self.m[1][0] * self.m[2][2] * self.m[3][3]
245            + self.m[0][0] * self.m[1][1] * self.m[2][2] * self.m[3][3]
246    }
247
248    fn multiply_scalar(&self, x: f32) -> Self {
249        ComputedTransform3D::new(
250            self.m[0][0] * x,
251            self.m[0][1] * x,
252            self.m[0][2] * x,
253            self.m[0][3] * x,
254            self.m[1][0] * x,
255            self.m[1][1] * x,
256            self.m[1][2] * x,
257            self.m[1][3] * x,
258            self.m[2][0] * x,
259            self.m[2][1] * x,
260            self.m[2][2] * x,
261            self.m[2][3] * x,
262            self.m[3][0] * x,
263            self.m[3][1] * x,
264            self.m[3][2] * x,
265            self.m[3][3] * x,
266        )
267    }
268
269    // Computes the matrix of a rect from a Vec<StyleTransform>
270    pub fn from_style_transform_vec(
271        t_vec: &[StyleTransform],
272        transform_origin: &StyleTransformOrigin,
273        percent_resolve_x: f32,
274        percent_resolve_y: f32,
275        rotation_mode: RotationMode,
276    ) -> Self {
277        // TODO: use correct SIMD optimization!
278        let mut matrix = Self::IDENTITY;
279        let use_avx =
280            INITIALIZED.load(AtomicOrdering::SeqCst) && USE_AVX.load(AtomicOrdering::SeqCst);
281        let use_sse = !use_avx
282            && INITIALIZED.load(AtomicOrdering::SeqCst)
283            && USE_SSE.load(AtomicOrdering::SeqCst);
284
285        if use_avx {
286            for t in t_vec.iter() {
287                #[cfg(target_arch = "x86_64")]
288                unsafe {
289                    matrix = matrix.then_avx8(&Self::from_style_transform(
290                        t,
291                        transform_origin,
292                        percent_resolve_x,
293                        percent_resolve_y,
294                        rotation_mode,
295                    ));
296                }
297            }
298        } else if use_sse {
299            for t in t_vec.iter() {
300                #[cfg(target_arch = "x86_64")]
301                unsafe {
302                    matrix = matrix.then_sse(&Self::from_style_transform(
303                        t,
304                        transform_origin,
305                        percent_resolve_x,
306                        percent_resolve_y,
307                        rotation_mode,
308                    ));
309                }
310            }
311        } else {
312            // fallback for everything else
313            for t in t_vec.iter() {
314                matrix = matrix.then(&Self::from_style_transform(
315                    t,
316                    transform_origin,
317                    percent_resolve_x,
318                    percent_resolve_y,
319                    rotation_mode,
320                ));
321            }
322        }
323
324        matrix
325    }
326
327    /// Creates a new transform from a style transform using the
328    /// parent width as a way to resolve for percentages
329    pub fn from_style_transform(
330        t: &StyleTransform,
331        transform_origin: &StyleTransformOrigin,
332        percent_resolve_x: f32,
333        percent_resolve_y: f32,
334        rotation_mode: RotationMode,
335    ) -> Self {
336        use azul_css::props::style::StyleTransform::*;
337        match t {
338            Matrix(mat2d) => {
339                let a = mat2d.a.get();
340                let b = mat2d.b.get();
341                let c = mat2d.c.get();
342                let d = mat2d.d.get();
343                let tx = mat2d.tx.get();
344                let ty = mat2d.ty.get();
345
346                Self::new_2d(a, b, c, d, tx, ty)
347            }
348            Matrix3D(mat3d) => {
349                let m11 = mat3d.m11.get();
350                let m12 = mat3d.m12.get();
351                let m13 = mat3d.m13.get();
352                let m14 = mat3d.m14.get();
353                let m21 = mat3d.m21.get();
354                let m22 = mat3d.m22.get();
355                let m23 = mat3d.m23.get();
356                let m24 = mat3d.m24.get();
357                let m31 = mat3d.m31.get();
358                let m32 = mat3d.m32.get();
359                let m33 = mat3d.m33.get();
360                let m34 = mat3d.m34.get();
361                let m41 = mat3d.m41.get();
362                let m42 = mat3d.m42.get();
363                let m43 = mat3d.m43.get();
364                let m44 = mat3d.m44.get();
365
366                Self::new(
367                    m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44,
368                )
369            }
370            Translate(trans2d) => {
371                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
372                Self::new_translation(
373                    trans2d
374                        .x
375                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE),
376                    trans2d
377                        .y
378                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE),
379                    0.0,
380                )
381            }
382            Translate3D(trans3d) => {
383                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
384                Self::new_translation(
385                    trans3d
386                        .x
387                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE),
388                    trans3d
389                        .y
390                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE),
391                    trans3d
392                        .z
393                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE), // ???
394                )
395            }
396            TranslateX(trans_x) => {
397                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
398                Self::new_translation(
399                    trans_x.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE),
400                    0.0,
401                    0.0,
402                )
403            }
404            TranslateY(trans_y) => {
405                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
406                Self::new_translation(
407                    0.0,
408                    trans_y.to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE),
409                    0.0,
410                )
411            }
412            TranslateZ(trans_z) => {
413                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
414                Self::new_translation(
415                    0.0,
416                    0.0,
417                    trans_z.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE),
418                )
419            } // ???
420            Rotate3D(rot3d) => {
421                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
422                let rotation_origin = (
423                    transform_origin
424                        .x
425                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE),
426                    transform_origin
427                        .y
428                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE),
429                );
430                Self::make_rotation(
431                    rotation_origin,
432                    rot3d.angle.to_degrees(),
433                    rot3d.x.get(),
434                    rot3d.y.get(),
435                    rot3d.z.get(),
436                    rotation_mode,
437                )
438            }
439            RotateX(angle_x) => {
440                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
441                let rotation_origin = (
442                    transform_origin
443                        .x
444                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE),
445                    transform_origin
446                        .y
447                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE),
448                );
449                Self::make_rotation(
450                    rotation_origin,
451                    angle_x.to_degrees(),
452                    1.0,
453                    0.0,
454                    0.0,
455                    rotation_mode,
456                )
457            }
458            RotateY(angle_y) => {
459                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
460                let rotation_origin = (
461                    transform_origin
462                        .x
463                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE),
464                    transform_origin
465                        .y
466                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE),
467                );
468                Self::make_rotation(
469                    rotation_origin,
470                    angle_y.to_degrees(),
471                    0.0,
472                    1.0,
473                    0.0,
474                    rotation_mode,
475                )
476            }
477            Rotate(angle_z) | RotateZ(angle_z) => {
478                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
479                let rotation_origin = (
480                    transform_origin
481                        .x
482                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE),
483                    transform_origin
484                        .y
485                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE),
486                );
487                Self::make_rotation(
488                    rotation_origin,
489                    angle_z.to_degrees(),
490                    0.0,
491                    0.0,
492                    1.0,
493                    rotation_mode,
494                )
495            }
496            Scale(scale2d) => Self::new_scale(scale2d.x.get(), scale2d.y.get(), 1.0),
497            Scale3D(scale3d) => Self::new_scale(scale3d.x.get(), scale3d.y.get(), scale3d.z.get()),
498            ScaleX(scale_x) => Self::new_scale(scale_x.normalized(), 1.0, 1.0),
499            ScaleY(scale_y) => Self::new_scale(1.0, scale_y.normalized(), 1.0),
500            ScaleZ(scale_z) => Self::new_scale(1.0, 1.0, scale_z.normalized()),
501            Skew(skew2d) => Self::new_skew(skew2d.x.to_degrees(), skew2d.y.to_degrees()),
502            SkewX(skew_x) => Self::new_skew(skew_x.to_degrees(), 0.0),
503            SkewY(skew_y) => Self::new_skew(0.0, skew_y.to_degrees()),
504            Perspective(px) => {
505                use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
506                Self::new_perspective(px.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE))
507            }
508        }
509    }
510
511    #[inline]
512    pub const fn new_scale(x: f32, y: f32, z: f32) -> Self {
513        Self::new(
514            x, 0.0, 0.0, 0.0, 0.0, y, 0.0, 0.0, 0.0, 0.0, z, 0.0, 0.0, 0.0, 0.0, 1.0,
515        )
516    }
517
518    #[inline]
519    pub const fn new_translation(x: f32, y: f32, z: f32) -> Self {
520        Self::new(
521            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, x, y, z, 1.0,
522        )
523    }
524
525    #[inline]
526    pub fn new_perspective(d: f32) -> Self {
527        Self::new(
528            1.0,
529            0.0,
530            0.0,
531            0.0,
532            0.0,
533            1.0,
534            0.0,
535            0.0,
536            0.0,
537            0.0,
538            1.0,
539            -1.0 / d,
540            0.0,
541            0.0,
542            0.0,
543            1.0,
544        )
545    }
546
547    /// Create a 3d rotation transform from an angle / axis.
548    /// The supplied axis must be normalized.
549    #[inline]
550    pub fn new_rotation(x: f32, y: f32, z: f32, theta_radians: f32) -> Self {
551        let xx = x * x;
552        let yy = y * y;
553        let zz = z * z;
554
555        let half_theta = theta_radians / 2.0;
556        let sc = half_theta.sin() * half_theta.cos();
557        let sq = half_theta.sin() * half_theta.sin();
558
559        Self::new(
560            1.0 - 2.0 * (yy + zz) * sq,
561            2.0 * (x * y * sq + z * sc),
562            2.0 * (x * z * sq - y * sc),
563            0.0,
564            2.0 * (x * y * sq - z * sc),
565            1.0 - 2.0 * (xx + zz) * sq,
566            2.0 * (y * z * sq + x * sc),
567            0.0,
568            2.0 * (x * z * sq + y * sc),
569            2.0 * (y * z * sq - x * sc),
570            1.0 - 2.0 * (xx + yy) * sq,
571            0.0,
572            0.0,
573            0.0,
574            0.0,
575            1.0,
576        )
577    }
578
579    #[inline]
580    pub fn new_skew(alpha: f32, beta: f32) -> Self {
581        let (sx, sy) = (beta.to_radians().tan(), alpha.to_radians().tan());
582        Self::new(
583            1.0, sx, 0.0, 0.0, sy, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
584        )
585    }
586
587    pub fn get_column_major(&self) -> Self {
588        ComputedTransform3D::new(
589            self.m[0][0],
590            self.m[1][0],
591            self.m[2][0],
592            self.m[3][0],
593            self.m[0][1],
594            self.m[1][1],
595            self.m[2][1],
596            self.m[3][1],
597            self.m[0][2],
598            self.m[1][2],
599            self.m[2][2],
600            self.m[3][2],
601            self.m[0][3],
602            self.m[1][3],
603            self.m[2][3],
604            self.m[3][3],
605        )
606    }
607
608    // Transforms a 2D point into the target coordinate space
609    #[must_use]
610    pub fn transform_point2d(&self, p: LogicalPosition) -> Option<LogicalPosition> {
611        let w =
612            p.x.mul_add(self.m[0][3], p.y.mul_add(self.m[1][3], self.m[3][3]));
613
614        if !w.is_sign_positive() {
615            return None;
616        }
617
618        let x =
619            p.x.mul_add(self.m[0][0], p.y.mul_add(self.m[1][0], self.m[3][0]));
620        let y =
621            p.x.mul_add(self.m[0][1], p.y.mul_add(self.m[1][1], self.m[3][1]));
622
623        Some(LogicalPosition { x: x / w, y: y / w })
624    }
625
626    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
627        // only scale the translation, don't scale anything else
628        self.m[3][0] *= scale_factor;
629        self.m[3][1] *= scale_factor;
630        self.m[3][2] *= scale_factor;
631    }
632
633    /// Computes the sum of two matrices while applying `other` AFTER the current matrix.
634    #[must_use]
635    #[inline]
636    pub fn then(&self, other: &Self) -> Self {
637        Self::new(
638            self.m[0][0].mul_add(
639                other.m[0][0],
640                self.m[0][1].mul_add(
641                    other.m[1][0],
642                    self.m[0][2].mul_add(other.m[2][0], self.m[0][3] * other.m[3][0]),
643                ),
644            ),
645            self.m[0][0].mul_add(
646                other.m[0][1],
647                self.m[0][1].mul_add(
648                    other.m[1][1],
649                    self.m[0][2].mul_add(other.m[2][1], self.m[0][3] * other.m[3][1]),
650                ),
651            ),
652            self.m[0][0].mul_add(
653                other.m[0][2],
654                self.m[0][1].mul_add(
655                    other.m[1][2],
656                    self.m[0][2].mul_add(other.m[2][2], self.m[0][3] * other.m[3][2]),
657                ),
658            ),
659            self.m[0][0].mul_add(
660                other.m[0][3],
661                self.m[0][1].mul_add(
662                    other.m[1][3],
663                    self.m[0][2].mul_add(other.m[2][3], self.m[0][3] * other.m[3][3]),
664                ),
665            ),
666            self.m[1][0].mul_add(
667                other.m[0][0],
668                self.m[1][1].mul_add(
669                    other.m[1][0],
670                    self.m[1][2].mul_add(other.m[2][0], self.m[1][3] * other.m[3][0]),
671                ),
672            ),
673            self.m[1][0].mul_add(
674                other.m[0][1],
675                self.m[1][1].mul_add(
676                    other.m[1][1],
677                    self.m[1][2].mul_add(other.m[2][1], self.m[1][3] * other.m[3][1]),
678                ),
679            ),
680            self.m[1][0].mul_add(
681                other.m[0][2],
682                self.m[1][1].mul_add(
683                    other.m[1][2],
684                    self.m[1][2].mul_add(other.m[2][2], self.m[1][3] * other.m[3][2]),
685                ),
686            ),
687            self.m[1][0].mul_add(
688                other.m[0][3],
689                self.m[1][1].mul_add(
690                    other.m[1][3],
691                    self.m[1][2].mul_add(other.m[2][3], self.m[1][3] * other.m[3][3]),
692                ),
693            ),
694            self.m[2][0].mul_add(
695                other.m[0][0],
696                self.m[2][1].mul_add(
697                    other.m[1][0],
698                    self.m[2][2].mul_add(other.m[2][0], self.m[2][3] * other.m[3][0]),
699                ),
700            ),
701            self.m[2][0].mul_add(
702                other.m[0][1],
703                self.m[2][1].mul_add(
704                    other.m[1][1],
705                    self.m[2][2].mul_add(other.m[2][1], self.m[2][3] * other.m[3][1]),
706                ),
707            ),
708            self.m[2][0].mul_add(
709                other.m[0][2],
710                self.m[2][1].mul_add(
711                    other.m[1][2],
712                    self.m[2][2].mul_add(other.m[2][2], self.m[2][3] * other.m[3][2]),
713                ),
714            ),
715            self.m[2][0].mul_add(
716                other.m[0][3],
717                self.m[2][1].mul_add(
718                    other.m[1][3],
719                    self.m[2][2].mul_add(other.m[2][3], self.m[2][3] * other.m[3][3]),
720                ),
721            ),
722            self.m[3][0].mul_add(
723                other.m[0][0],
724                self.m[3][1].mul_add(
725                    other.m[1][0],
726                    self.m[3][2].mul_add(other.m[2][0], self.m[3][3] * other.m[3][0]),
727                ),
728            ),
729            self.m[3][0].mul_add(
730                other.m[0][1],
731                self.m[3][1].mul_add(
732                    other.m[1][1],
733                    self.m[3][2].mul_add(other.m[2][1], self.m[3][3] * other.m[3][1]),
734                ),
735            ),
736            self.m[3][0].mul_add(
737                other.m[0][2],
738                self.m[3][1].mul_add(
739                    other.m[1][2],
740                    self.m[3][2].mul_add(other.m[2][2], self.m[3][3] * other.m[3][2]),
741                ),
742            ),
743            self.m[3][0].mul_add(
744                other.m[0][3],
745                self.m[3][1].mul_add(
746                    other.m[1][3],
747                    self.m[3][2].mul_add(other.m[2][3], self.m[3][3] * other.m[3][3]),
748                ),
749            ),
750        )
751    }
752
753    // credit: https://gist.github.com/rygorous/4172889
754
755    // linear combination:
756    // a[0] * B.row[0] + a[1] * B.row[1] + a[2] * B.row[2] + a[3] * B.row[3]
757    #[cfg(target_arch = "x86_64")]
758    #[inline]
759    unsafe fn linear_combine_sse(a: [f32; 4], b: &ComputedTransform3D) -> [f32; 4] {
760        use core::{
761            arch::x86_64::{__m128, _mm_add_ps, _mm_mul_ps, _mm_shuffle_ps},
762            mem,
763        };
764
765        let a: __m128 = mem::transmute(a);
766        let mut result = _mm_mul_ps(_mm_shuffle_ps(a, a, 0x00), mem::transmute(b.m[0]));
767        result = _mm_add_ps(
768            result,
769            _mm_mul_ps(_mm_shuffle_ps(a, a, 0x55), mem::transmute(b.m[1])),
770        );
771        result = _mm_add_ps(
772            result,
773            _mm_mul_ps(_mm_shuffle_ps(a, a, 0xaa), mem::transmute(b.m[2])),
774        );
775        result = _mm_add_ps(
776            result,
777            _mm_mul_ps(_mm_shuffle_ps(a, a, 0xff), mem::transmute(b.m[3])),
778        );
779
780        mem::transmute(result)
781    }
782
783    #[cfg(target_arch = "x86_64")]
784    #[inline]
785    pub unsafe fn then_sse(&self, other: &Self) -> Self {
786        Self {
787            m: [
788                Self::linear_combine_sse(self.m[0], other),
789                Self::linear_combine_sse(self.m[1], other),
790                Self::linear_combine_sse(self.m[2], other),
791                Self::linear_combine_sse(self.m[3], other),
792            ],
793        }
794    }
795
796    // dual linear combination using AVX instructions on YMM regs
797    #[cfg(target_arch = "x86_64")]
798    pub unsafe fn linear_combine_avx8(
799        a01: core::arch::x86_64::__m256,
800        b: &ComputedTransform3D,
801    ) -> core::arch::x86_64::__m256 {
802        use core::{
803            arch::x86_64::{_mm256_add_ps, _mm256_broadcast_ps, _mm256_mul_ps, _mm256_shuffle_ps},
804            mem,
805        };
806
807        let mut result = _mm256_mul_ps(
808            _mm256_shuffle_ps(a01, a01, 0x00),
809            _mm256_broadcast_ps(mem::transmute(&b.m[0])),
810        );
811        result = _mm256_add_ps(
812            result,
813            _mm256_mul_ps(
814                _mm256_shuffle_ps(a01, a01, 0x55),
815                _mm256_broadcast_ps(mem::transmute(&b.m[1])),
816            ),
817        );
818        result = _mm256_add_ps(
819            result,
820            _mm256_mul_ps(
821                _mm256_shuffle_ps(a01, a01, 0xaa),
822                _mm256_broadcast_ps(mem::transmute(&b.m[2])),
823            ),
824        );
825        result = _mm256_add_ps(
826            result,
827            _mm256_mul_ps(
828                _mm256_shuffle_ps(a01, a01, 0xff),
829                _mm256_broadcast_ps(mem::transmute(&b.m[3])),
830            ),
831        );
832        result
833    }
834
835    #[cfg(target_arch = "x86_64")]
836    #[inline]
837    pub unsafe fn then_avx8(&self, other: &Self) -> Self {
838        use core::{
839            arch::x86_64::{__m256, _mm256_loadu_ps, _mm256_storeu_ps, _mm256_zeroupper},
840            mem,
841        };
842
843        _mm256_zeroupper();
844
845        let a01: __m256 = _mm256_loadu_ps(mem::transmute(&self.m[0][0]));
846        let a23: __m256 = _mm256_loadu_ps(mem::transmute(&self.m[2][0]));
847
848        let out01x = Self::linear_combine_avx8(a01, other);
849        let out23x = Self::linear_combine_avx8(a23, other);
850
851        let mut out = Self {
852            m: [self.m[0], self.m[1], self.m[2], self.m[3]],
853        };
854
855        _mm256_storeu_ps(mem::transmute(&mut out.m[0][0]), out01x);
856        _mm256_storeu_ps(mem::transmute(&mut out.m[2][0]), out23x);
857
858        out
859    }
860
861    // NOTE: webrenders RENDERING has a different
862    // rotation mode (positive / negative angle)
863    #[inline]
864    pub fn make_rotation(
865        rotation_origin: (f32, f32),
866        mut degrees: f32,
867        axis_x: f32,
868        axis_y: f32,
869        axis_z: f32,
870        // see documentation for RotationMode
871        rotation_mode: RotationMode,
872    ) -> Self {
873        degrees = match rotation_mode {
874            // CSS rotations are clockwise
875            RotationMode::ForWebRender => -degrees,
876            // hit-testing turns counter-clockwise
877            RotationMode::ForHitTesting => degrees,
878        };
879
880        let (origin_x, origin_y) = rotation_origin;
881        let pre_transform = Self::new_translation(-origin_x, -origin_y, -0.0);
882        let post_transform = Self::new_translation(origin_x, origin_y, 0.0);
883        let theta = 2.0_f32 * core::f32::consts::PI - degrees.to_radians();
884        let rotate_transform =
885            Self::new_rotation(axis_x, axis_y, axis_z, theta).then(&Self::IDENTITY);
886
887        pre_transform.then(&rotate_transform).then(&post_transform)
888    }
889}