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

            
14
use core::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
15

            
16
use azul_css::props::style::{StyleTransform, StyleTransformOrigin};
17

            
18
use crate::geom::LogicalPosition;
19

            
20
/// CPU feature detection: true if initialization has been performed
21
pub static INITIALIZED: AtomicBool = AtomicBool::new(false);
22
/// CPU feature detection: true if AVX instructions are available
23
pub static USE_AVX: AtomicBool = AtomicBool::new(false);
24
/// CPU feature detection: true if SSE instructions are available
25
pub 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)]
33
pub 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)]
57
pub struct ComputedTransform3D {
58
    /// The 4x4 matrix in row-major format
59
    pub m: [[f32; 4]; 4],
60
}
61

            
62
impl 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
126
    pub const fn new(
77
126
        m11: f32,
78
126
        m12: f32,
79
126
        m13: f32,
80
126
        m14: f32,
81
126
        m21: f32,
82
126
        m22: f32,
83
126
        m23: f32,
84
126
        m24: f32,
85
126
        m31: f32,
86
126
        m32: f32,
87
126
        m33: f32,
88
126
        m34: f32,
89
126
        m41: f32,
90
126
        m42: f32,
91
126
        m43: f32,
92
126
        m44: f32,
93
126
    ) -> Self {
94
126
        Self {
95
126
            m: [
96
126
                [m11, m12, m13, m14],
97
126
                [m21, m22, m23, m24],
98
126
                [m31, m32, m33, m34],
99
126
                [m41, m42, m43, m44],
100
126
            ],
101
126
        }
102
126
    }
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
126
    pub fn from_style_transform_vec(
274
126
        t_vec: &[StyleTransform],
275
126
        transform_origin: &StyleTransformOrigin,
276
126
        percent_resolve_x: f32,
277
126
        percent_resolve_y: f32,
278
126
        rotation_mode: RotationMode,
279
126
    ) -> Self {
280
        // Uses AVX or SSE SIMD when available on x86_64
281
126
        let mut matrix = Self::IDENTITY;
282
126
        let use_avx =
283
126
            INITIALIZED.load(AtomicOrdering::Relaxed) && USE_AVX.load(AtomicOrdering::Relaxed);
284
126
        let use_sse = !use_avx
285
            && INITIALIZED.load(AtomicOrdering::Relaxed)
286
            && USE_SSE.load(AtomicOrdering::Relaxed);
287

            
288
126
        if use_avx {
289
126
            for t in t_vec.iter() {
290
                #[cfg(target_arch = "x86_64")]
291
126
                unsafe {
292
126
                    matrix = matrix.then_avx8(&Self::from_style_transform(
293
126
                        t,
294
126
                        transform_origin,
295
126
                        percent_resolve_x,
296
126
                        percent_resolve_y,
297
126
                        rotation_mode,
298
126
                    ));
299
126
                }
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
126
        matrix
328
126
    }
329

            
330
    /// Creates a new transform from a style transform using the
331
    /// parent width as a way to resolve for percentages
332
126
    fn from_style_transform(
333
126
        t: &StyleTransform,
334
126
        transform_origin: &StyleTransformOrigin,
335
126
        percent_resolve_x: f32,
336
126
        percent_resolve_y: f32,
337
126
        rotation_mode: RotationMode,
338
126
    ) -> Self {
339
        use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
340
        use azul_css::props::style::StyleTransform::*;
341
126
        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
126
            TranslateX(trans_x) => {
402

            
403
126
                Self::new_translation(
404
126
                    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
126
    }
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
126
    pub const fn new_translation(x: f32, y: f32, z: f32) -> Self {
529
126
        Self::new(
530
126
            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
126
    }
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
252
    unsafe fn linear_combine_avx8(
817
252
        a01: core::arch::x86_64::__m256,
818
252
        b: &ComputedTransform3D,
819
252
    ) -> 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
252
        let mut result = _mm256_mul_ps(
826
252
            _mm256_shuffle_ps(a01, a01, 0x00),
827
252
            _mm256_broadcast_ps(mem::transmute(&b.m[0])),
828
        );
829
252
        result = _mm256_add_ps(
830
252
            result,
831
252
            _mm256_mul_ps(
832
252
                _mm256_shuffle_ps(a01, a01, 0x55),
833
252
                _mm256_broadcast_ps(mem::transmute(&b.m[1])),
834
252
            ),
835
252
        );
836
252
        result = _mm256_add_ps(
837
252
            result,
838
252
            _mm256_mul_ps(
839
252
                _mm256_shuffle_ps(a01, a01, 0xaa),
840
252
                _mm256_broadcast_ps(mem::transmute(&b.m[2])),
841
252
            ),
842
252
        );
843
252
        result = _mm256_add_ps(
844
252
            result,
845
252
            _mm256_mul_ps(
846
252
                _mm256_shuffle_ps(a01, a01, 0xff),
847
252
                _mm256_broadcast_ps(mem::transmute(&b.m[3])),
848
252
            ),
849
252
        );
850
252
        result
851
252
    }
852

            
853
    /// Multiplies this matrix by `other` using AVX instructions.
854
    #[cfg(target_arch = "x86_64")]
855
    #[inline]
856
126
    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
126
        _mm256_zeroupper();
863

            
864
126
        let a01: __m256 = _mm256_loadu_ps(mem::transmute(&self.m[0][0]));
865
126
        let a23: __m256 = _mm256_loadu_ps(mem::transmute(&self.m[2][0]));
866

            
867
126
        let out01x = Self::linear_combine_avx8(a01, other);
868
126
        let out23x = Self::linear_combine_avx8(a23, other);
869

            
870
126
        let mut out = Self {
871
126
            m: [self.m[0], self.m[1], self.m[2], self.m[3]],
872
126
        };
873

            
874
126
        _mm256_storeu_ps(mem::transmute(&mut out.m[0][0]), out01x);
875
126
        _mm256_storeu_ps(mem::transmute(&mut out.m[2][0]), out23x);
876

            
877
126
        out
878
126
    }
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
}