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    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    #[must_use]
123    pub fn inverse(&self) -> Self {
124        let det = self.determinant();
125
126        if det.abs() < f32::EPSILON {
127            return Self::IDENTITY;
128        }
129
130        let m = ComputedTransform3D::new(
131            self.m[1][2] * self.m[2][3] * self.m[3][1] - self.m[1][3] * self.m[2][2] * self.m[3][1]
132                + self.m[1][3] * self.m[2][1] * self.m[3][2]
133                - self.m[1][1] * self.m[2][3] * self.m[3][2]
134                - self.m[1][2] * self.m[2][1] * self.m[3][3]
135                + self.m[1][1] * self.m[2][2] * self.m[3][3],
136            self.m[0][3] * self.m[2][2] * self.m[3][1]
137                - self.m[0][2] * self.m[2][3] * self.m[3][1]
138                - self.m[0][3] * self.m[2][1] * self.m[3][2]
139                + self.m[0][1] * self.m[2][3] * self.m[3][2]
140                + self.m[0][2] * self.m[2][1] * self.m[3][3]
141                - self.m[0][1] * self.m[2][2] * self.m[3][3],
142            self.m[0][2] * self.m[1][3] * self.m[3][1] - self.m[0][3] * self.m[1][2] * self.m[3][1]
143                + self.m[0][3] * self.m[1][1] * self.m[3][2]
144                - self.m[0][1] * self.m[1][3] * self.m[3][2]
145                - self.m[0][2] * self.m[1][1] * self.m[3][3]
146                + self.m[0][1] * self.m[1][2] * self.m[3][3],
147            self.m[0][3] * self.m[1][2] * self.m[2][1]
148                - self.m[0][2] * self.m[1][3] * self.m[2][1]
149                - self.m[0][3] * self.m[1][1] * self.m[2][2]
150                + self.m[0][1] * self.m[1][3] * self.m[2][2]
151                + self.m[0][2] * self.m[1][1] * self.m[2][3]
152                - self.m[0][1] * self.m[1][2] * self.m[2][3],
153            self.m[1][3] * self.m[2][2] * self.m[3][0]
154                - self.m[1][2] * self.m[2][3] * self.m[3][0]
155                - self.m[1][3] * self.m[2][0] * self.m[3][2]
156                + self.m[1][0] * self.m[2][3] * self.m[3][2]
157                + self.m[1][2] * self.m[2][0] * self.m[3][3]
158                - self.m[1][0] * self.m[2][2] * self.m[3][3],
159            self.m[0][2] * self.m[2][3] * self.m[3][0] - self.m[0][3] * self.m[2][2] * self.m[3][0]
160                + self.m[0][3] * self.m[2][0] * self.m[3][2]
161                - self.m[0][0] * self.m[2][3] * self.m[3][2]
162                - self.m[0][2] * self.m[2][0] * self.m[3][3]
163                + self.m[0][0] * self.m[2][2] * self.m[3][3],
164            self.m[0][3] * self.m[1][2] * self.m[3][0]
165                - self.m[0][2] * self.m[1][3] * self.m[3][0]
166                - self.m[0][3] * self.m[1][0] * self.m[3][2]
167                + self.m[0][0] * self.m[1][3] * self.m[3][2]
168                + self.m[0][2] * self.m[1][0] * self.m[3][3]
169                - self.m[0][0] * self.m[1][2] * self.m[3][3],
170            self.m[0][2] * self.m[1][3] * self.m[2][0] - self.m[0][3] * self.m[1][2] * self.m[2][0]
171                + self.m[0][3] * self.m[1][0] * self.m[2][2]
172                - self.m[0][0] * self.m[1][3] * self.m[2][2]
173                - self.m[0][2] * self.m[1][0] * self.m[2][3]
174                + self.m[0][0] * self.m[1][2] * self.m[2][3],
175            self.m[1][1] * self.m[2][3] * self.m[3][0] - self.m[1][3] * self.m[2][1] * self.m[3][0]
176                + self.m[1][3] * self.m[2][0] * self.m[3][1]
177                - self.m[1][0] * self.m[2][3] * self.m[3][1]
178                - self.m[1][1] * self.m[2][0] * self.m[3][3]
179                + self.m[1][0] * self.m[2][1] * self.m[3][3],
180            self.m[0][3] * self.m[2][1] * self.m[3][0]
181                - self.m[0][1] * self.m[2][3] * self.m[3][0]
182                - self.m[0][3] * self.m[2][0] * self.m[3][1]
183                + self.m[0][0] * self.m[2][3] * self.m[3][1]
184                + self.m[0][1] * self.m[2][0] * self.m[3][3]
185                - self.m[0][0] * self.m[2][1] * self.m[3][3],
186            self.m[0][1] * self.m[1][3] * self.m[3][0] - self.m[0][3] * self.m[1][1] * self.m[3][0]
187                + self.m[0][3] * self.m[1][0] * self.m[3][1]
188                - self.m[0][0] * self.m[1][3] * self.m[3][1]
189                - self.m[0][1] * self.m[1][0] * self.m[3][3]
190                + self.m[0][0] * self.m[1][1] * self.m[3][3],
191            self.m[0][3] * self.m[1][1] * self.m[2][0]
192                - self.m[0][1] * self.m[1][3] * self.m[2][0]
193                - self.m[0][3] * self.m[1][0] * self.m[2][1]
194                + self.m[0][0] * self.m[1][3] * self.m[2][1]
195                + self.m[0][1] * self.m[1][0] * self.m[2][3]
196                - self.m[0][0] * self.m[1][1] * self.m[2][3],
197            self.m[1][2] * self.m[2][1] * self.m[3][0]
198                - self.m[1][1] * self.m[2][2] * self.m[3][0]
199                - self.m[1][2] * self.m[2][0] * self.m[3][1]
200                + self.m[1][0] * self.m[2][2] * self.m[3][1]
201                + self.m[1][1] * self.m[2][0] * self.m[3][2]
202                - self.m[1][0] * self.m[2][1] * self.m[3][2],
203            self.m[0][1] * self.m[2][2] * self.m[3][0] - self.m[0][2] * self.m[2][1] * self.m[3][0]
204                + self.m[0][2] * self.m[2][0] * self.m[3][1]
205                - self.m[0][0] * self.m[2][2] * self.m[3][1]
206                - self.m[0][1] * self.m[2][0] * self.m[3][2]
207                + self.m[0][0] * self.m[2][1] * self.m[3][2],
208            self.m[0][2] * self.m[1][1] * self.m[3][0]
209                - self.m[0][1] * self.m[1][2] * self.m[3][0]
210                - self.m[0][2] * self.m[1][0] * self.m[3][1]
211                + self.m[0][0] * self.m[1][2] * self.m[3][1]
212                + self.m[0][1] * self.m[1][0] * self.m[3][2]
213                - self.m[0][0] * self.m[1][1] * self.m[3][2],
214            self.m[0][1] * self.m[1][2] * self.m[2][0] - self.m[0][2] * self.m[1][1] * self.m[2][0]
215                + self.m[0][2] * self.m[1][0] * self.m[2][1]
216                - self.m[0][0] * self.m[1][2] * self.m[2][1]
217                - self.m[0][1] * self.m[1][0] * self.m[2][2]
218                + self.m[0][0] * self.m[1][1] * self.m[2][2],
219        );
220
221        m.multiply_scalar(1.0 / det)
222    }
223
224    fn determinant(&self) -> f32 {
225        self.m[0][3] * self.m[1][2] * self.m[2][1] * self.m[3][0]
226            - self.m[0][2] * self.m[1][3] * self.m[2][1] * self.m[3][0]
227            - self.m[0][3] * self.m[1][1] * self.m[2][2] * self.m[3][0]
228            + self.m[0][1] * self.m[1][3] * self.m[2][2] * self.m[3][0]
229            + self.m[0][2] * self.m[1][1] * self.m[2][3] * self.m[3][0]
230            - self.m[0][1] * self.m[1][2] * self.m[2][3] * self.m[3][0]
231            - self.m[0][3] * self.m[1][2] * self.m[2][0] * self.m[3][1]
232            + self.m[0][2] * self.m[1][3] * self.m[2][0] * self.m[3][1]
233            + self.m[0][3] * self.m[1][0] * self.m[2][2] * self.m[3][1]
234            - self.m[0][0] * self.m[1][3] * self.m[2][2] * self.m[3][1]
235            - self.m[0][2] * self.m[1][0] * self.m[2][3] * self.m[3][1]
236            + self.m[0][0] * self.m[1][2] * self.m[2][3] * self.m[3][1]
237            + self.m[0][3] * self.m[1][1] * self.m[2][0] * self.m[3][2]
238            - self.m[0][1] * self.m[1][3] * self.m[2][0] * self.m[3][2]
239            - self.m[0][3] * self.m[1][0] * self.m[2][1] * self.m[3][2]
240            + self.m[0][0] * self.m[1][3] * self.m[2][1] * self.m[3][2]
241            + self.m[0][1] * self.m[1][0] * self.m[2][3] * self.m[3][2]
242            - self.m[0][0] * self.m[1][1] * self.m[2][3] * self.m[3][2]
243            - self.m[0][2] * self.m[1][1] * self.m[2][0] * self.m[3][3]
244            + self.m[0][1] * self.m[1][2] * self.m[2][0] * self.m[3][3]
245            + self.m[0][2] * self.m[1][0] * self.m[2][1] * self.m[3][3]
246            - self.m[0][0] * self.m[1][2] * self.m[2][1] * self.m[3][3]
247            - self.m[0][1] * self.m[1][0] * self.m[2][2] * self.m[3][3]
248            + self.m[0][0] * self.m[1][1] * self.m[2][2] * self.m[3][3]
249    }
250
251    fn multiply_scalar(&self, x: f32) -> Self {
252        ComputedTransform3D::new(
253            self.m[0][0] * x,
254            self.m[0][1] * x,
255            self.m[0][2] * x,
256            self.m[0][3] * x,
257            self.m[1][0] * x,
258            self.m[1][1] * x,
259            self.m[1][2] * x,
260            self.m[1][3] * x,
261            self.m[2][0] * x,
262            self.m[2][1] * x,
263            self.m[2][2] * x,
264            self.m[2][3] * x,
265            self.m[3][0] * x,
266            self.m[3][1] * x,
267            self.m[3][2] * x,
268            self.m[3][3] * x,
269        )
270    }
271
272    /// Computes the matrix of a rect from a `&[StyleTransform]`.
273    pub fn from_style_transform_vec(
274        t_vec: &[StyleTransform],
275        transform_origin: &StyleTransformOrigin,
276        percent_resolve_x: f32,
277        percent_resolve_y: f32,
278        rotation_mode: RotationMode,
279    ) -> Self {
280        // Uses AVX or SSE SIMD when available on x86_64
281        let mut matrix = Self::IDENTITY;
282        let use_avx =
283            INITIALIZED.load(AtomicOrdering::Relaxed) && USE_AVX.load(AtomicOrdering::Relaxed);
284        let use_sse = !use_avx
285            && INITIALIZED.load(AtomicOrdering::Relaxed)
286            && USE_SSE.load(AtomicOrdering::Relaxed);
287
288        if use_avx {
289            for t in t_vec.iter() {
290                #[cfg(target_arch = "x86_64")]
291                unsafe {
292                    matrix = matrix.then_avx8(&Self::from_style_transform(
293                        t,
294                        transform_origin,
295                        percent_resolve_x,
296                        percent_resolve_y,
297                        rotation_mode,
298                    ));
299                }
300            }
301        } else if use_sse {
302            for t in t_vec.iter() {
303                #[cfg(target_arch = "x86_64")]
304                unsafe {
305                    matrix = matrix.then_sse(&Self::from_style_transform(
306                        t,
307                        transform_origin,
308                        percent_resolve_x,
309                        percent_resolve_y,
310                        rotation_mode,
311                    ));
312                }
313            }
314        } else {
315            // fallback for everything else
316            for t in t_vec.iter() {
317                matrix = matrix.then(&Self::from_style_transform(
318                    t,
319                    transform_origin,
320                    percent_resolve_x,
321                    percent_resolve_y,
322                    rotation_mode,
323                ));
324            }
325        }
326
327        matrix
328    }
329
330    /// Creates a new transform from a style transform using the
331    /// parent width as a way to resolve for percentages
332    fn from_style_transform(
333        t: &StyleTransform,
334        transform_origin: &StyleTransformOrigin,
335        percent_resolve_x: f32,
336        percent_resolve_y: f32,
337        rotation_mode: RotationMode,
338    ) -> Self {
339        use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
340        use azul_css::props::style::StyleTransform::*;
341        match t {
342            Matrix(mat2d) => {
343                let a = mat2d.a.get();
344                let b = mat2d.b.get();
345                let c = mat2d.c.get();
346                let d = mat2d.d.get();
347                let tx = mat2d.tx.get();
348                let ty = mat2d.ty.get();
349
350                Self::new_2d(a, b, c, d, tx, ty)
351            }
352            Matrix3D(mat3d) => {
353                let m11 = mat3d.m11.get();
354                let m12 = mat3d.m12.get();
355                let m13 = mat3d.m13.get();
356                let m14 = mat3d.m14.get();
357                let m21 = mat3d.m21.get();
358                let m22 = mat3d.m22.get();
359                let m23 = mat3d.m23.get();
360                let m24 = mat3d.m24.get();
361                let m31 = mat3d.m31.get();
362                let m32 = mat3d.m32.get();
363                let m33 = mat3d.m33.get();
364                let m34 = mat3d.m34.get();
365                let m41 = mat3d.m41.get();
366                let m42 = mat3d.m42.get();
367                let m43 = mat3d.m43.get();
368                let m44 = mat3d.m44.get();
369
370                Self::new(
371                    m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44,
372                )
373            }
374            Translate(trans2d) => {
375
376                Self::new_translation(
377                    trans2d
378                        .x
379                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
380                    trans2d
381                        .y
382                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
383                    0.0,
384                )
385            }
386            Translate3D(trans3d) => {
387
388                Self::new_translation(
389                    trans3d
390                        .x
391                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
392                    trans3d
393                        .y
394                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
395                    trans3d
396                        .z
397                        // CSS has no containing block for Z-axis percentages; use X as fallback
398                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
399                )
400            }
401            TranslateX(trans_x) => {
402
403                Self::new_translation(
404                    trans_x.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
405                    0.0,
406                    0.0,
407                )
408            }
409            TranslateY(trans_y) => {
410
411                Self::new_translation(
412                    0.0,
413                    trans_y.to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
414                    0.0,
415                )
416            }
417            TranslateZ(trans_z) => {
418
419                Self::new_translation(
420                    0.0,
421                    0.0,
422                    trans_z.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
423                )
424            } // CSS has no containing block for Z-axis percentages; use X as fallback
425            Rotate3D(rot3d) => {
426
427                let rotation_origin = (
428                    transform_origin
429                        .x
430                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
431                    transform_origin
432                        .y
433                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
434                );
435                Self::make_rotation(
436                    rotation_origin,
437                    rot3d.angle.to_degrees(),
438                    rot3d.x.get(),
439                    rot3d.y.get(),
440                    rot3d.z.get(),
441                    rotation_mode,
442                )
443            }
444            RotateX(angle_x) => {
445
446                let rotation_origin = (
447                    transform_origin
448                        .x
449                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
450                    transform_origin
451                        .y
452                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
453                );
454                Self::make_rotation(
455                    rotation_origin,
456                    angle_x.to_degrees(),
457                    1.0,
458                    0.0,
459                    0.0,
460                    rotation_mode,
461                )
462            }
463            RotateY(angle_y) => {
464
465                let rotation_origin = (
466                    transform_origin
467                        .x
468                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
469                    transform_origin
470                        .y
471                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
472                );
473                Self::make_rotation(
474                    rotation_origin,
475                    angle_y.to_degrees(),
476                    0.0,
477                    1.0,
478                    0.0,
479                    rotation_mode,
480                )
481            }
482            Rotate(angle_z) | RotateZ(angle_z) => {
483
484                let rotation_origin = (
485                    transform_origin
486                        .x
487                        .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
488                    transform_origin
489                        .y
490                        .to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
491                );
492                Self::make_rotation(
493                    rotation_origin,
494                    angle_z.to_degrees(),
495                    0.0,
496                    0.0,
497                    1.0,
498                    rotation_mode,
499                )
500            }
501            Scale(scale2d) => Self::new_scale(scale2d.x.get(), scale2d.y.get(), 1.0),
502            Scale3D(scale3d) => Self::new_scale(scale3d.x.get(), scale3d.y.get(), scale3d.z.get()),
503            ScaleX(scale_x) => Self::new_scale(scale_x.normalized(), 1.0, 1.0),
504            ScaleY(scale_y) => Self::new_scale(1.0, scale_y.normalized(), 1.0),
505            ScaleZ(scale_z) => Self::new_scale(1.0, 1.0, scale_z.normalized()),
506            Skew(skew2d) => Self::new_skew(skew2d.x.to_degrees(), skew2d.y.to_degrees()),
507            SkewX(skew_x) => Self::new_skew(skew_x.to_degrees(), 0.0),
508            SkewY(skew_y) => Self::new_skew(0.0, skew_y.to_degrees()),
509            Perspective(px) => {
510
511                Self::new_perspective(px.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
512            }
513        }
514    }
515
516    /// Creates a scaling matrix with independent scale factors per axis.
517    #[must_use]
518    #[inline]
519    pub const fn new_scale(x: f32, y: f32, z: f32) -> Self {
520        Self::new(
521            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,
522        )
523    }
524
525    /// Creates a translation matrix that moves by `(x, y, z)`.
526    #[must_use]
527    #[inline]
528    pub const fn new_translation(x: f32, y: f32, z: f32) -> Self {
529        Self::new(
530            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,
531        )
532    }
533
534    /// Creates a perspective projection matrix with distance `d`.
535    #[must_use]
536    #[inline]
537    fn new_perspective(d: f32) -> Self {
538        Self::new(
539            1.0,
540            0.0,
541            0.0,
542            0.0,
543            0.0,
544            1.0,
545            0.0,
546            0.0,
547            0.0,
548            0.0,
549            1.0,
550            -1.0 / d,
551            0.0,
552            0.0,
553            0.0,
554            1.0,
555        )
556    }
557
558    /// Create a 3d rotation transform from an angle / axis.
559    /// The supplied axis must be normalized.
560    #[must_use]
561    #[inline]
562    fn new_rotation(x: f32, y: f32, z: f32, theta_radians: f32) -> Self {
563        let xx = x * x;
564        let yy = y * y;
565        let zz = z * z;
566
567        let half_theta = theta_radians / 2.0;
568        let sc = half_theta.sin() * half_theta.cos();
569        let sq = half_theta.sin() * half_theta.sin();
570
571        Self::new(
572            1.0 - 2.0 * (yy + zz) * sq,
573            2.0 * (x * y * sq + z * sc),
574            2.0 * (x * z * sq - y * sc),
575            0.0,
576            2.0 * (x * y * sq - z * sc),
577            1.0 - 2.0 * (xx + zz) * sq,
578            2.0 * (y * z * sq + x * sc),
579            0.0,
580            2.0 * (x * z * sq + y * sc),
581            2.0 * (y * z * sq - x * sc),
582            1.0 - 2.0 * (xx + yy) * sq,
583            0.0,
584            0.0,
585            0.0,
586            0.0,
587            1.0,
588        )
589    }
590
591    /// Creates a 2D skew matrix from angles in degrees.
592    #[must_use]
593    #[inline]
594    fn new_skew(alpha: f32, beta: f32) -> Self {
595        let (sx, sy) = (beta.to_radians().tan(), alpha.to_radians().tan());
596        Self::new(
597            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,
598        )
599    }
600
601    /// Returns this matrix transposed to column-major layout.
602    #[must_use]
603    pub(crate) fn get_column_major(&self) -> Self {
604        ComputedTransform3D::new(
605            self.m[0][0],
606            self.m[1][0],
607            self.m[2][0],
608            self.m[3][0],
609            self.m[0][1],
610            self.m[1][1],
611            self.m[2][1],
612            self.m[3][1],
613            self.m[0][2],
614            self.m[1][2],
615            self.m[2][2],
616            self.m[3][2],
617            self.m[0][3],
618            self.m[1][3],
619            self.m[2][3],
620            self.m[3][3],
621        )
622    }
623
624    /// Transforms a 2D point into the target coordinate space.
625    #[must_use]
626    pub fn transform_point2d(&self, p: LogicalPosition) -> Option<LogicalPosition> {
627        let w =
628            p.x.mul_add(self.m[0][3], p.y.mul_add(self.m[1][3], self.m[3][3]));
629
630        if !w.is_sign_positive() {
631            return None;
632        }
633
634        let x =
635            p.x.mul_add(self.m[0][0], p.y.mul_add(self.m[1][0], self.m[3][0]));
636        let y =
637            p.x.mul_add(self.m[0][1], p.y.mul_add(self.m[1][1], self.m[3][1]));
638
639        Some(LogicalPosition { x: x / w, y: y / w })
640    }
641
642    /// Scales the translation components of this matrix by `scale_factor` for DPI adjustment.
643    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
644        // only scale the translation, don't scale anything else
645        self.m[3][0] *= scale_factor;
646        self.m[3][1] *= scale_factor;
647        self.m[3][2] *= scale_factor;
648    }
649
650    /// Multiplies this matrix by `other`, applying `other` AFTER the current matrix.
651    #[must_use]
652    #[inline]
653    pub fn then(&self, other: &Self) -> Self {
654        Self::new(
655            self.m[0][0].mul_add(
656                other.m[0][0],
657                self.m[0][1].mul_add(
658                    other.m[1][0],
659                    self.m[0][2].mul_add(other.m[2][0], self.m[0][3] * other.m[3][0]),
660                ),
661            ),
662            self.m[0][0].mul_add(
663                other.m[0][1],
664                self.m[0][1].mul_add(
665                    other.m[1][1],
666                    self.m[0][2].mul_add(other.m[2][1], self.m[0][3] * other.m[3][1]),
667                ),
668            ),
669            self.m[0][0].mul_add(
670                other.m[0][2],
671                self.m[0][1].mul_add(
672                    other.m[1][2],
673                    self.m[0][2].mul_add(other.m[2][2], self.m[0][3] * other.m[3][2]),
674                ),
675            ),
676            self.m[0][0].mul_add(
677                other.m[0][3],
678                self.m[0][1].mul_add(
679                    other.m[1][3],
680                    self.m[0][2].mul_add(other.m[2][3], self.m[0][3] * other.m[3][3]),
681                ),
682            ),
683            self.m[1][0].mul_add(
684                other.m[0][0],
685                self.m[1][1].mul_add(
686                    other.m[1][0],
687                    self.m[1][2].mul_add(other.m[2][0], self.m[1][3] * other.m[3][0]),
688                ),
689            ),
690            self.m[1][0].mul_add(
691                other.m[0][1],
692                self.m[1][1].mul_add(
693                    other.m[1][1],
694                    self.m[1][2].mul_add(other.m[2][1], self.m[1][3] * other.m[3][1]),
695                ),
696            ),
697            self.m[1][0].mul_add(
698                other.m[0][2],
699                self.m[1][1].mul_add(
700                    other.m[1][2],
701                    self.m[1][2].mul_add(other.m[2][2], self.m[1][3] * other.m[3][2]),
702                ),
703            ),
704            self.m[1][0].mul_add(
705                other.m[0][3],
706                self.m[1][1].mul_add(
707                    other.m[1][3],
708                    self.m[1][2].mul_add(other.m[2][3], self.m[1][3] * other.m[3][3]),
709                ),
710            ),
711            self.m[2][0].mul_add(
712                other.m[0][0],
713                self.m[2][1].mul_add(
714                    other.m[1][0],
715                    self.m[2][2].mul_add(other.m[2][0], self.m[2][3] * other.m[3][0]),
716                ),
717            ),
718            self.m[2][0].mul_add(
719                other.m[0][1],
720                self.m[2][1].mul_add(
721                    other.m[1][1],
722                    self.m[2][2].mul_add(other.m[2][1], self.m[2][3] * other.m[3][1]),
723                ),
724            ),
725            self.m[2][0].mul_add(
726                other.m[0][2],
727                self.m[2][1].mul_add(
728                    other.m[1][2],
729                    self.m[2][2].mul_add(other.m[2][2], self.m[2][3] * other.m[3][2]),
730                ),
731            ),
732            self.m[2][0].mul_add(
733                other.m[0][3],
734                self.m[2][1].mul_add(
735                    other.m[1][3],
736                    self.m[2][2].mul_add(other.m[2][3], self.m[2][3] * other.m[3][3]),
737                ),
738            ),
739            self.m[3][0].mul_add(
740                other.m[0][0],
741                self.m[3][1].mul_add(
742                    other.m[1][0],
743                    self.m[3][2].mul_add(other.m[2][0], self.m[3][3] * other.m[3][0]),
744                ),
745            ),
746            self.m[3][0].mul_add(
747                other.m[0][1],
748                self.m[3][1].mul_add(
749                    other.m[1][1],
750                    self.m[3][2].mul_add(other.m[2][1], self.m[3][3] * other.m[3][1]),
751                ),
752            ),
753            self.m[3][0].mul_add(
754                other.m[0][2],
755                self.m[3][1].mul_add(
756                    other.m[1][2],
757                    self.m[3][2].mul_add(other.m[2][2], self.m[3][3] * other.m[3][2]),
758                ),
759            ),
760            self.m[3][0].mul_add(
761                other.m[0][3],
762                self.m[3][1].mul_add(
763                    other.m[1][3],
764                    self.m[3][2].mul_add(other.m[2][3], self.m[3][3] * other.m[3][3]),
765                ),
766            ),
767        )
768    }
769
770    // credit: https://gist.github.com/rygorous/4172889
771
772    // linear combination:
773    // a[0] * B.row[0] + a[1] * B.row[1] + a[2] * B.row[2] + a[3] * B.row[3]
774    #[cfg(target_arch = "x86_64")]
775    #[inline]
776    unsafe fn linear_combine_sse(a: [f32; 4], b: &ComputedTransform3D) -> [f32; 4] {
777        use core::{
778            arch::x86_64::{__m128, _mm_add_ps, _mm_mul_ps, _mm_shuffle_ps},
779            mem,
780        };
781
782        let a: __m128 = mem::transmute(a);
783        let mut result = _mm_mul_ps(_mm_shuffle_ps(a, a, 0x00), mem::transmute(b.m[0]));
784        result = _mm_add_ps(
785            result,
786            _mm_mul_ps(_mm_shuffle_ps(a, a, 0x55), mem::transmute(b.m[1])),
787        );
788        result = _mm_add_ps(
789            result,
790            _mm_mul_ps(_mm_shuffle_ps(a, a, 0xaa), mem::transmute(b.m[2])),
791        );
792        result = _mm_add_ps(
793            result,
794            _mm_mul_ps(_mm_shuffle_ps(a, a, 0xff), mem::transmute(b.m[3])),
795        );
796
797        mem::transmute(result)
798    }
799
800    /// Multiplies this matrix by `other` using SSE instructions.
801    #[cfg(target_arch = "x86_64")]
802    #[inline]
803    unsafe fn then_sse(&self, other: &Self) -> Self {
804        Self {
805            m: [
806                Self::linear_combine_sse(self.m[0], other),
807                Self::linear_combine_sse(self.m[1], other),
808                Self::linear_combine_sse(self.m[2], other),
809                Self::linear_combine_sse(self.m[3], other),
810            ],
811        }
812    }
813
814    /// Dual linear combination using AVX instructions on YMM registers.
815    #[cfg(target_arch = "x86_64")]
816    unsafe fn linear_combine_avx8(
817        a01: core::arch::x86_64::__m256,
818        b: &ComputedTransform3D,
819    ) -> core::arch::x86_64::__m256 {
820        use core::{
821            arch::x86_64::{_mm256_add_ps, _mm256_broadcast_ps, _mm256_mul_ps, _mm256_shuffle_ps},
822            mem,
823        };
824
825        let mut result = _mm256_mul_ps(
826            _mm256_shuffle_ps(a01, a01, 0x00),
827            _mm256_broadcast_ps(mem::transmute(&b.m[0])),
828        );
829        result = _mm256_add_ps(
830            result,
831            _mm256_mul_ps(
832                _mm256_shuffle_ps(a01, a01, 0x55),
833                _mm256_broadcast_ps(mem::transmute(&b.m[1])),
834            ),
835        );
836        result = _mm256_add_ps(
837            result,
838            _mm256_mul_ps(
839                _mm256_shuffle_ps(a01, a01, 0xaa),
840                _mm256_broadcast_ps(mem::transmute(&b.m[2])),
841            ),
842        );
843        result = _mm256_add_ps(
844            result,
845            _mm256_mul_ps(
846                _mm256_shuffle_ps(a01, a01, 0xff),
847                _mm256_broadcast_ps(mem::transmute(&b.m[3])),
848            ),
849        );
850        result
851    }
852
853    /// Multiplies this matrix by `other` using AVX instructions.
854    #[cfg(target_arch = "x86_64")]
855    #[inline]
856    unsafe fn then_avx8(&self, other: &Self) -> Self {
857        use core::{
858            arch::x86_64::{__m256, _mm256_loadu_ps, _mm256_storeu_ps, _mm256_zeroupper},
859            mem,
860        };
861
862        _mm256_zeroupper();
863
864        let a01: __m256 = _mm256_loadu_ps(mem::transmute(&self.m[0][0]));
865        let a23: __m256 = _mm256_loadu_ps(mem::transmute(&self.m[2][0]));
866
867        let out01x = Self::linear_combine_avx8(a01, other);
868        let out23x = Self::linear_combine_avx8(a23, other);
869
870        let mut out = Self {
871            m: [self.m[0], self.m[1], self.m[2], self.m[3]],
872        };
873
874        _mm256_storeu_ps(mem::transmute(&mut out.m[0][0]), out01x);
875        _mm256_storeu_ps(mem::transmute(&mut out.m[2][0]), out23x);
876
877        out
878    }
879
880    /// Creates a rotation matrix around the given axis, adjusted for the coordinate system.
881    #[must_use]
882    #[inline]
883    fn make_rotation(
884        rotation_origin: (f32, f32),
885        mut degrees: f32,
886        axis_x: f32,
887        axis_y: f32,
888        axis_z: f32,
889        // see documentation for RotationMode
890        rotation_mode: RotationMode,
891    ) -> Self {
892        degrees = match rotation_mode {
893            // CSS rotations are clockwise
894            RotationMode::ForWebRender => -degrees,
895            // hit-testing turns counter-clockwise
896            RotationMode::ForHitTesting => degrees,
897        };
898
899        let (origin_x, origin_y) = rotation_origin;
900        let pre_transform = Self::new_translation(-origin_x, -origin_y, 0.0);
901        let post_transform = Self::new_translation(origin_x, origin_y, 0.0);
902        let theta = 2.0_f32 * core::f32::consts::PI - degrees.to_radians();
903        let rotate_transform =
904            Self::new_rotation(axis_x, axis_y, axis_z, theta);
905
906        pre_transform.then(&rotate_transform).then(&post_transform)
907    }
908}