1use core::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
15
16use azul_css::props::style::{StyleTransform, StyleTransformOrigin};
17
18use crate::geom::LogicalPosition;
19
20pub static INITIALIZED: AtomicBool = AtomicBool::new(false);
22pub static USE_AVX: AtomicBool = AtomicBool::new(false);
24pub static USE_SSE: AtomicBool = AtomicBool::new(false);
26
27#[derive(Debug, Copy, Clone)]
33pub enum RotationMode {
34 ForWebRender,
36 ForHitTesting,
38}
39
40#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
56#[repr(C)]
57pub struct ComputedTransform3D {
58 pub m: [[f32; 4]; 4],
60}
61
62impl ComputedTransform3D {
63 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 #[must_use]
77 pub const fn new(
78 m11: f32,
79 m12: f32,
80 m13: f32,
81 m14: f32,
82 m21: f32,
83 m22: f32,
84 m23: f32,
85 m24: f32,
86 m31: f32,
87 m32: f32,
88 m33: f32,
89 m34: f32,
90 m41: f32,
91 m42: f32,
92 m43: f32,
93 m44: f32,
94 ) -> Self {
95 Self {
96 m: [
97 [m11, m12, m13, m14],
98 [m21, m22, m23, m24],
99 [m31, m32, m33, m34],
100 [m41, m42, m43, m44],
101 ],
102 }
103 }
104
105 const fn new_2d(m11: f32, m12: f32, m21: f32, m22: f32, m41: f32, m42: f32) -> Self {
112 Self::new(
113 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,
114 )
115 }
116
117 #[must_use]
124 #[allow(clippy::suboptimal_flops)] pub fn inverse(&self) -> Self {
126 let det = self.determinant();
127
128 if det.abs() < f32::EPSILON {
129 return Self::IDENTITY;
130 }
131
132 let m = Self::new(
133 self.m[1][2] * self.m[2][3] * self.m[3][1] - self.m[1][3] * self.m[2][2] * self.m[3][1]
134 + self.m[1][3] * self.m[2][1] * self.m[3][2]
135 - self.m[1][1] * self.m[2][3] * self.m[3][2]
136 - self.m[1][2] * self.m[2][1] * self.m[3][3]
137 + self.m[1][1] * self.m[2][2] * self.m[3][3],
138 self.m[0][3] * self.m[2][2] * self.m[3][1]
139 - self.m[0][2] * self.m[2][3] * self.m[3][1]
140 - self.m[0][3] * self.m[2][1] * self.m[3][2]
141 + self.m[0][1] * self.m[2][3] * self.m[3][2]
142 + self.m[0][2] * self.m[2][1] * self.m[3][3]
143 - self.m[0][1] * self.m[2][2] * self.m[3][3],
144 self.m[0][2] * self.m[1][3] * self.m[3][1] - self.m[0][3] * self.m[1][2] * self.m[3][1]
145 + self.m[0][3] * self.m[1][1] * self.m[3][2]
146 - self.m[0][1] * self.m[1][3] * self.m[3][2]
147 - self.m[0][2] * self.m[1][1] * self.m[3][3]
148 + self.m[0][1] * self.m[1][2] * self.m[3][3],
149 self.m[0][3] * self.m[1][2] * self.m[2][1]
150 - self.m[0][2] * self.m[1][3] * self.m[2][1]
151 - self.m[0][3] * self.m[1][1] * self.m[2][2]
152 + self.m[0][1] * self.m[1][3] * self.m[2][2]
153 + self.m[0][2] * self.m[1][1] * self.m[2][3]
154 - self.m[0][1] * self.m[1][2] * self.m[2][3],
155 self.m[1][3] * self.m[2][2] * self.m[3][0]
156 - self.m[1][2] * self.m[2][3] * self.m[3][0]
157 - self.m[1][3] * self.m[2][0] * self.m[3][2]
158 + self.m[1][0] * self.m[2][3] * self.m[3][2]
159 + self.m[1][2] * self.m[2][0] * self.m[3][3]
160 - self.m[1][0] * self.m[2][2] * self.m[3][3],
161 self.m[0][2] * self.m[2][3] * self.m[3][0] - self.m[0][3] * self.m[2][2] * self.m[3][0]
162 + self.m[0][3] * self.m[2][0] * self.m[3][2]
163 - self.m[0][0] * self.m[2][3] * self.m[3][2]
164 - self.m[0][2] * self.m[2][0] * self.m[3][3]
165 + self.m[0][0] * self.m[2][2] * self.m[3][3],
166 self.m[0][3] * self.m[1][2] * self.m[3][0]
167 - self.m[0][2] * self.m[1][3] * self.m[3][0]
168 - self.m[0][3] * self.m[1][0] * self.m[3][2]
169 + self.m[0][0] * self.m[1][3] * self.m[3][2]
170 + self.m[0][2] * self.m[1][0] * self.m[3][3]
171 - self.m[0][0] * self.m[1][2] * self.m[3][3],
172 self.m[0][2] * self.m[1][3] * self.m[2][0] - self.m[0][3] * self.m[1][2] * self.m[2][0]
173 + self.m[0][3] * self.m[1][0] * self.m[2][2]
174 - self.m[0][0] * self.m[1][3] * self.m[2][2]
175 - self.m[0][2] * self.m[1][0] * self.m[2][3]
176 + self.m[0][0] * self.m[1][2] * self.m[2][3],
177 self.m[1][1] * self.m[2][3] * self.m[3][0] - self.m[1][3] * self.m[2][1] * self.m[3][0]
178 + self.m[1][3] * self.m[2][0] * self.m[3][1]
179 - self.m[1][0] * self.m[2][3] * self.m[3][1]
180 - self.m[1][1] * self.m[2][0] * self.m[3][3]
181 + self.m[1][0] * self.m[2][1] * self.m[3][3],
182 self.m[0][3] * self.m[2][1] * self.m[3][0]
183 - self.m[0][1] * self.m[2][3] * self.m[3][0]
184 - self.m[0][3] * self.m[2][0] * self.m[3][1]
185 + self.m[0][0] * self.m[2][3] * self.m[3][1]
186 + self.m[0][1] * self.m[2][0] * self.m[3][3]
187 - self.m[0][0] * self.m[2][1] * self.m[3][3],
188 self.m[0][1] * self.m[1][3] * self.m[3][0] - self.m[0][3] * self.m[1][1] * self.m[3][0]
189 + self.m[0][3] * self.m[1][0] * self.m[3][1]
190 - self.m[0][0] * self.m[1][3] * self.m[3][1]
191 - self.m[0][1] * self.m[1][0] * self.m[3][3]
192 + self.m[0][0] * self.m[1][1] * self.m[3][3],
193 self.m[0][3] * self.m[1][1] * self.m[2][0]
194 - self.m[0][1] * self.m[1][3] * self.m[2][0]
195 - self.m[0][3] * self.m[1][0] * self.m[2][1]
196 + self.m[0][0] * self.m[1][3] * self.m[2][1]
197 + self.m[0][1] * self.m[1][0] * self.m[2][3]
198 - self.m[0][0] * self.m[1][1] * self.m[2][3],
199 self.m[1][2] * self.m[2][1] * self.m[3][0]
200 - self.m[1][1] * self.m[2][2] * self.m[3][0]
201 - self.m[1][2] * self.m[2][0] * self.m[3][1]
202 + self.m[1][0] * self.m[2][2] * self.m[3][1]
203 + self.m[1][1] * self.m[2][0] * self.m[3][2]
204 - self.m[1][0] * self.m[2][1] * self.m[3][2],
205 self.m[0][1] * self.m[2][2] * self.m[3][0] - self.m[0][2] * self.m[2][1] * self.m[3][0]
206 + self.m[0][2] * self.m[2][0] * self.m[3][1]
207 - self.m[0][0] * self.m[2][2] * self.m[3][1]
208 - self.m[0][1] * self.m[2][0] * self.m[3][2]
209 + self.m[0][0] * self.m[2][1] * self.m[3][2],
210 self.m[0][2] * self.m[1][1] * self.m[3][0]
211 - self.m[0][1] * self.m[1][2] * self.m[3][0]
212 - self.m[0][2] * self.m[1][0] * self.m[3][1]
213 + self.m[0][0] * self.m[1][2] * self.m[3][1]
214 + self.m[0][1] * self.m[1][0] * self.m[3][2]
215 - self.m[0][0] * self.m[1][1] * self.m[3][2],
216 self.m[0][1] * self.m[1][2] * self.m[2][0] - self.m[0][2] * self.m[1][1] * self.m[2][0]
217 + self.m[0][2] * self.m[1][0] * self.m[2][1]
218 - self.m[0][0] * self.m[1][2] * self.m[2][1]
219 - self.m[0][1] * self.m[1][0] * self.m[2][2]
220 + self.m[0][0] * self.m[1][1] * self.m[2][2],
221 );
222
223 m.multiply_scalar(1.0 / det)
224 }
225
226 #[allow(clippy::suboptimal_flops)] fn determinant(&self) -> f32 {
228 let m = |i: usize, j: usize| f64::from(self.m[i][j]);
234 let det = m(0, 3) * m(1, 2) * m(2, 1) * m(3, 0)
235 - m(0, 2) * m(1, 3) * m(2, 1) * m(3, 0)
236 - m(0, 3) * m(1, 1) * m(2, 2) * m(3, 0)
237 + m(0, 1) * m(1, 3) * m(2, 2) * m(3, 0)
238 + m(0, 2) * m(1, 1) * m(2, 3) * m(3, 0)
239 - m(0, 1) * m(1, 2) * m(2, 3) * m(3, 0)
240 - m(0, 3) * m(1, 2) * m(2, 0) * m(3, 1)
241 + m(0, 2) * m(1, 3) * m(2, 0) * m(3, 1)
242 + m(0, 3) * m(1, 0) * m(2, 2) * m(3, 1)
243 - m(0, 0) * m(1, 3) * m(2, 2) * m(3, 1)
244 - m(0, 2) * m(1, 0) * m(2, 3) * m(3, 1)
245 + m(0, 0) * m(1, 2) * m(2, 3) * m(3, 1)
246 + m(0, 3) * m(1, 1) * m(2, 0) * m(3, 2)
247 - m(0, 1) * m(1, 3) * m(2, 0) * m(3, 2)
248 - m(0, 3) * m(1, 0) * m(2, 1) * m(3, 2)
249 + m(0, 0) * m(1, 3) * m(2, 1) * m(3, 2)
250 + m(0, 1) * m(1, 0) * m(2, 3) * m(3, 2)
251 - m(0, 0) * m(1, 1) * m(2, 3) * m(3, 2)
252 - m(0, 2) * m(1, 1) * m(2, 0) * m(3, 3)
253 + m(0, 1) * m(1, 2) * m(2, 0) * m(3, 3)
254 + m(0, 2) * m(1, 0) * m(2, 1) * m(3, 3)
255 - m(0, 0) * m(1, 2) * m(2, 1) * m(3, 3)
256 - m(0, 1) * m(1, 0) * m(2, 2) * m(3, 3)
257 + m(0, 0) * m(1, 1) * m(2, 2) * m(3, 3);
258 #[allow(clippy::cast_possible_truncation)]
259 let det = det as f32;
261 det
262 }
263
264 fn multiply_scalar(&self, x: f32) -> Self {
265 Self::new(
266 self.m[0][0] * x,
267 self.m[0][1] * x,
268 self.m[0][2] * x,
269 self.m[0][3] * x,
270 self.m[1][0] * x,
271 self.m[1][1] * x,
272 self.m[1][2] * x,
273 self.m[1][3] * x,
274 self.m[2][0] * x,
275 self.m[2][1] * x,
276 self.m[2][2] * x,
277 self.m[2][3] * x,
278 self.m[3][0] * x,
279 self.m[3][1] * x,
280 self.m[3][2] * x,
281 self.m[3][3] * x,
282 )
283 }
284
285 pub fn from_style_transform_vec(
287 t_vec: &[StyleTransform],
288 transform_origin: &StyleTransformOrigin,
289 percent_resolve_x: f32,
290 percent_resolve_y: f32,
291 rotation_mode: RotationMode,
292 ) -> Self {
293 use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
320 use azul_css::props::basic::PixelValue;
321 let no_origin = StyleTransformOrigin {
322 x: PixelValue::const_px(0),
323 y: PixelValue::const_px(0),
324 };
325 let mut matrix = Self::IDENTITY;
326 let use_avx =
327 INITIALIZED.load(AtomicOrdering::Relaxed) && USE_AVX.load(AtomicOrdering::Relaxed);
328 let use_sse = !use_avx
329 && INITIALIZED.load(AtomicOrdering::Relaxed)
330 && USE_SSE.load(AtomicOrdering::Relaxed);
331
332 if use_avx {
333 for t in t_vec {
334 let component = Self::from_style_transform(
335 t,
336 &no_origin,
337 percent_resolve_x,
338 percent_resolve_y,
339 rotation_mode,
340 );
341 #[cfg(target_arch = "x86_64")]
345 unsafe {
346 matrix = component.then_avx8(&matrix);
347 }
348 }
349 } else if use_sse {
350 for t in t_vec {
351 let component = Self::from_style_transform(
352 t,
353 &no_origin,
354 percent_resolve_x,
355 percent_resolve_y,
356 rotation_mode,
357 );
358 #[cfg(target_arch = "x86_64")]
362 unsafe {
363 matrix = component.then_sse(&matrix);
364 }
365 }
366 } else {
367 for t in t_vec {
369 let component = Self::from_style_transform(
370 t,
371 &no_origin,
372 percent_resolve_x,
373 percent_resolve_y,
374 rotation_mode,
375 );
376 matrix = component.then(&matrix);
377 }
378 }
379
380 let origin_x = transform_origin.x.to_pixels_internal(
383 percent_resolve_x,
384 DEFAULT_FONT_SIZE,
385 DEFAULT_FONT_SIZE,
386 );
387 let origin_y = transform_origin.y.to_pixels_internal(
388 percent_resolve_y,
389 DEFAULT_FONT_SIZE,
390 DEFAULT_FONT_SIZE,
391 );
392 if origin_x == 0.0 && origin_y == 0.0 {
393 return matrix;
394 }
395 Self::new_translation(-origin_x, -origin_y, 0.0)
396 .then(&matrix)
397 .then(&Self::new_translation(origin_x, origin_y, 0.0))
398 }
399
400 #[allow(clippy::many_single_char_names)] #[allow(clippy::too_many_lines)] fn from_style_transform(
405 t: &StyleTransform,
406 transform_origin: &StyleTransformOrigin,
407 percent_resolve_x: f32,
408 percent_resolve_y: f32,
409 rotation_mode: RotationMode,
410 ) -> Self {
411 use azul_css::props::basic::pixel::DEFAULT_FONT_SIZE;
412 use azul_css::props::style::StyleTransform::{
413 Matrix, Matrix3D, Perspective, Rotate, Rotate3D, RotateX, RotateY, RotateZ, Scale,
414 Scale3D, ScaleX, ScaleY, ScaleZ, Skew, SkewX, SkewY, Translate, Translate3D,
415 TranslateX, TranslateY, TranslateZ,
416 };
417 match t {
418 Matrix(mat2d) => {
419 let a = mat2d.a.get();
420 let b = mat2d.b.get();
421 let c = mat2d.c.get();
422 let d = mat2d.d.get();
423 let tx = mat2d.tx.get();
424 let ty = mat2d.ty.get();
425
426 Self::new_2d(a, b, c, d, tx, ty)
427 }
428 Matrix3D(mat3d) => {
429 let m11 = mat3d.m11.get();
430 let m12 = mat3d.m12.get();
431 let m13 = mat3d.m13.get();
432 let m14 = mat3d.m14.get();
433 let m21 = mat3d.m21.get();
434 let m22 = mat3d.m22.get();
435 let m23 = mat3d.m23.get();
436 let m24 = mat3d.m24.get();
437 let m31 = mat3d.m31.get();
438 let m32 = mat3d.m32.get();
439 let m33 = mat3d.m33.get();
440 let m34 = mat3d.m34.get();
441 let m41 = mat3d.m41.get();
442 let m42 = mat3d.m42.get();
443 let m43 = mat3d.m43.get();
444 let m44 = mat3d.m44.get();
445
446 Self::new(
447 m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44,
448 )
449 }
450 Translate(trans2d) => Self::new_translation(
451 trans2d.x.to_pixels_internal(
452 percent_resolve_x,
453 DEFAULT_FONT_SIZE,
454 DEFAULT_FONT_SIZE,
455 ),
456 trans2d.y.to_pixels_internal(
457 percent_resolve_y,
458 DEFAULT_FONT_SIZE,
459 DEFAULT_FONT_SIZE,
460 ),
461 0.0,
462 ),
463 Translate3D(trans3d) => {
464 Self::new_translation(
465 trans3d.x.to_pixels_internal(
466 percent_resolve_x,
467 DEFAULT_FONT_SIZE,
468 DEFAULT_FONT_SIZE,
469 ),
470 trans3d.y.to_pixels_internal(
471 percent_resolve_y,
472 DEFAULT_FONT_SIZE,
473 DEFAULT_FONT_SIZE,
474 ),
475 trans3d
476 .z
477 .to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
479 )
480 }
481 TranslateX(trans_x) => Self::new_translation(
482 trans_x.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
483 0.0,
484 0.0,
485 ),
486 TranslateY(trans_y) => Self::new_translation(
487 0.0,
488 trans_y.to_pixels_internal(percent_resolve_y, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
489 0.0,
490 ),
491 TranslateZ(trans_z) => Self::new_translation(
492 0.0,
493 0.0,
494 trans_z.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
495 ), Rotate3D(rot3d) => {
497 let rotation_origin = (
498 transform_origin.x.to_pixels_internal(
499 percent_resolve_x,
500 DEFAULT_FONT_SIZE,
501 DEFAULT_FONT_SIZE,
502 ),
503 transform_origin.y.to_pixels_internal(
504 percent_resolve_y,
505 DEFAULT_FONT_SIZE,
506 DEFAULT_FONT_SIZE,
507 ),
508 );
509 Self::make_rotation(
510 rotation_origin,
511 rot3d.angle.to_degrees(),
512 rot3d.x.get(),
513 rot3d.y.get(),
514 rot3d.z.get(),
515 rotation_mode,
516 )
517 }
518 RotateX(angle_x) => {
519 let rotation_origin = (
520 transform_origin.x.to_pixels_internal(
521 percent_resolve_x,
522 DEFAULT_FONT_SIZE,
523 DEFAULT_FONT_SIZE,
524 ),
525 transform_origin.y.to_pixels_internal(
526 percent_resolve_y,
527 DEFAULT_FONT_SIZE,
528 DEFAULT_FONT_SIZE,
529 ),
530 );
531 Self::make_rotation(
532 rotation_origin,
533 angle_x.to_degrees(),
534 1.0,
535 0.0,
536 0.0,
537 rotation_mode,
538 )
539 }
540 RotateY(angle_y) => {
541 let rotation_origin = (
542 transform_origin.x.to_pixels_internal(
543 percent_resolve_x,
544 DEFAULT_FONT_SIZE,
545 DEFAULT_FONT_SIZE,
546 ),
547 transform_origin.y.to_pixels_internal(
548 percent_resolve_y,
549 DEFAULT_FONT_SIZE,
550 DEFAULT_FONT_SIZE,
551 ),
552 );
553 Self::make_rotation(
554 rotation_origin,
555 angle_y.to_degrees(),
556 0.0,
557 1.0,
558 0.0,
559 rotation_mode,
560 )
561 }
562 Rotate(angle_z) | RotateZ(angle_z) => {
563 let rotation_origin = (
564 transform_origin.x.to_pixels_internal(
565 percent_resolve_x,
566 DEFAULT_FONT_SIZE,
567 DEFAULT_FONT_SIZE,
568 ),
569 transform_origin.y.to_pixels_internal(
570 percent_resolve_y,
571 DEFAULT_FONT_SIZE,
572 DEFAULT_FONT_SIZE,
573 ),
574 );
575 Self::make_rotation(
576 rotation_origin,
577 angle_z.to_degrees(),
578 0.0,
579 0.0,
580 1.0,
581 rotation_mode,
582 )
583 }
584 Scale(scale2d) => Self::new_scale(scale2d.x.get(), scale2d.y.get(), 1.0),
585 Scale3D(scale3d) => Self::new_scale(scale3d.x.get(), scale3d.y.get(), scale3d.z.get()),
586 ScaleX(scale_x) => Self::new_scale(scale_x.normalized(), 1.0, 1.0),
587 ScaleY(scale_y) => Self::new_scale(1.0, scale_y.normalized(), 1.0),
588 ScaleZ(scale_z) => Self::new_scale(1.0, 1.0, scale_z.normalized()),
589 Skew(skew2d) => Self::new_skew(skew2d.x.to_degrees(), skew2d.y.to_degrees()),
590 SkewX(skew_x) => Self::new_skew(skew_x.to_degrees(), 0.0),
591 SkewY(skew_y) => Self::new_skew(0.0, skew_y.to_degrees()),
592 Perspective(px) => {
593 let origin_x = transform_origin.x.to_pixels_internal(
600 percent_resolve_x,
601 DEFAULT_FONT_SIZE,
602 DEFAULT_FONT_SIZE,
603 );
604 let origin_y = transform_origin.y.to_pixels_internal(
605 percent_resolve_y,
606 DEFAULT_FONT_SIZE,
607 DEFAULT_FONT_SIZE,
608 );
609 let d =
610 px.to_pixels_internal(percent_resolve_x, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
611 Self::new_translation(-origin_x, -origin_y, 0.0)
612 .then(&Self::new_perspective(d))
613 .then(&Self::new_translation(origin_x, origin_y, 0.0))
614 }
615 }
616 }
617
618 #[must_use]
626 pub const fn plane_homography(&self) -> [f32; 9] {
627 [
628 self.m[0][0],
629 self.m[0][1],
630 self.m[0][3],
631 self.m[1][0],
632 self.m[1][1],
633 self.m[1][3],
634 self.m[3][0],
635 self.m[3][1],
636 self.m[3][3],
637 ]
638 }
639
640 #[must_use]
642 pub const fn is_plane_affine(&self) -> bool {
643 let (a, b, w) = (self.m[0][3], self.m[1][3], self.m[3][3]);
644 a > -1e-7 && a < 1e-7 && b > -1e-7 && b < 1e-7 && w > 1.0 - 1e-6 && w < 1.0 + 1e-6
645 }
646
647 #[must_use]
649 #[inline]
650 pub const fn new_scale(x: f32, y: f32, z: f32) -> Self {
651 Self::new(
652 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,
653 )
654 }
655
656 #[must_use]
658 #[inline]
659 pub const fn new_translation(x: f32, y: f32, z: f32) -> Self {
660 Self::new(
661 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,
662 )
663 }
664
665 #[must_use]
667 #[inline]
668 fn new_perspective(d: f32) -> Self {
669 Self::new(
670 1.0,
671 0.0,
672 0.0,
673 0.0,
674 0.0,
675 1.0,
676 0.0,
677 0.0,
678 0.0,
679 0.0,
680 1.0,
681 -1.0 / d,
682 0.0,
683 0.0,
684 0.0,
685 1.0,
686 )
687 }
688
689 #[must_use]
692 #[inline]
693 #[allow(clippy::suboptimal_flops)] fn new_rotation(x: f32, y: f32, z: f32, theta_radians: f32) -> Self {
695 let xx = x * x;
696 let yy = y * y;
697 let zz = z * z;
698
699 let half_theta = theta_radians / 2.0;
700 let sc = half_theta.sin() * half_theta.cos();
701 let sq = half_theta.sin() * half_theta.sin();
702
703 Self::new(
704 1.0 - 2.0 * (yy + zz) * sq,
705 2.0 * (x * y * sq + z * sc),
706 2.0 * (x * z * sq - y * sc),
707 0.0,
708 2.0 * (x * y * sq - z * sc),
709 1.0 - 2.0 * (xx + zz) * sq,
710 2.0 * (y * z * sq + x * sc),
711 0.0,
712 2.0 * (x * z * sq + y * sc),
713 2.0 * (y * z * sq - x * sc),
714 1.0 - 2.0 * (xx + yy) * sq,
715 0.0,
716 0.0,
717 0.0,
718 0.0,
719 1.0,
720 )
721 }
722
723 #[must_use]
725 #[inline]
726 fn new_skew(alpha: f32, beta: f32) -> Self {
727 let (sx, sy) = (beta.to_radians().tan(), alpha.to_radians().tan());
728 Self::new(
729 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,
730 )
731 }
732
733 #[must_use]
735 pub(crate) const fn get_column_major(&self) -> Self {
736 Self::new(
737 self.m[0][0],
738 self.m[1][0],
739 self.m[2][0],
740 self.m[3][0],
741 self.m[0][1],
742 self.m[1][1],
743 self.m[2][1],
744 self.m[3][1],
745 self.m[0][2],
746 self.m[1][2],
747 self.m[2][2],
748 self.m[3][2],
749 self.m[0][3],
750 self.m[1][3],
751 self.m[2][3],
752 self.m[3][3],
753 )
754 }
755
756 #[must_use]
758 pub fn transform_point2d(&self, p: LogicalPosition) -> Option<LogicalPosition> {
759 let w =
760 p.x.mul_add(self.m[0][3], p.y.mul_add(self.m[1][3], self.m[3][3]));
761
762 if !w.is_sign_positive() {
763 return None;
764 }
765
766 let x =
767 p.x.mul_add(self.m[0][0], p.y.mul_add(self.m[1][0], self.m[3][0]));
768 let y =
769 p.x.mul_add(self.m[0][1], p.y.mul_add(self.m[1][1], self.m[3][1]));
770
771 Some(LogicalPosition { x: x / w, y: y / w })
772 }
773
774 pub fn scale_for_dpi(&mut self, scale_factor: f32) {
776 self.m[3][0] *= scale_factor;
778 self.m[3][1] *= scale_factor;
779 self.m[3][2] *= scale_factor;
780 }
781
782 #[must_use]
784 #[inline]
785 #[allow(clippy::too_many_lines)] pub fn then(&self, other: &Self) -> Self {
787 Self::new(
788 self.m[0][0].mul_add(
789 other.m[0][0],
790 self.m[0][1].mul_add(
791 other.m[1][0],
792 self.m[0][2].mul_add(other.m[2][0], self.m[0][3] * other.m[3][0]),
793 ),
794 ),
795 self.m[0][0].mul_add(
796 other.m[0][1],
797 self.m[0][1].mul_add(
798 other.m[1][1],
799 self.m[0][2].mul_add(other.m[2][1], self.m[0][3] * other.m[3][1]),
800 ),
801 ),
802 self.m[0][0].mul_add(
803 other.m[0][2],
804 self.m[0][1].mul_add(
805 other.m[1][2],
806 self.m[0][2].mul_add(other.m[2][2], self.m[0][3] * other.m[3][2]),
807 ),
808 ),
809 self.m[0][0].mul_add(
810 other.m[0][3],
811 self.m[0][1].mul_add(
812 other.m[1][3],
813 self.m[0][2].mul_add(other.m[2][3], self.m[0][3] * other.m[3][3]),
814 ),
815 ),
816 self.m[1][0].mul_add(
817 other.m[0][0],
818 self.m[1][1].mul_add(
819 other.m[1][0],
820 self.m[1][2].mul_add(other.m[2][0], self.m[1][3] * other.m[3][0]),
821 ),
822 ),
823 self.m[1][0].mul_add(
824 other.m[0][1],
825 self.m[1][1].mul_add(
826 other.m[1][1],
827 self.m[1][2].mul_add(other.m[2][1], self.m[1][3] * other.m[3][1]),
828 ),
829 ),
830 self.m[1][0].mul_add(
831 other.m[0][2],
832 self.m[1][1].mul_add(
833 other.m[1][2],
834 self.m[1][2].mul_add(other.m[2][2], self.m[1][3] * other.m[3][2]),
835 ),
836 ),
837 self.m[1][0].mul_add(
838 other.m[0][3],
839 self.m[1][1].mul_add(
840 other.m[1][3],
841 self.m[1][2].mul_add(other.m[2][3], self.m[1][3] * other.m[3][3]),
842 ),
843 ),
844 self.m[2][0].mul_add(
845 other.m[0][0],
846 self.m[2][1].mul_add(
847 other.m[1][0],
848 self.m[2][2].mul_add(other.m[2][0], self.m[2][3] * other.m[3][0]),
849 ),
850 ),
851 self.m[2][0].mul_add(
852 other.m[0][1],
853 self.m[2][1].mul_add(
854 other.m[1][1],
855 self.m[2][2].mul_add(other.m[2][1], self.m[2][3] * other.m[3][1]),
856 ),
857 ),
858 self.m[2][0].mul_add(
859 other.m[0][2],
860 self.m[2][1].mul_add(
861 other.m[1][2],
862 self.m[2][2].mul_add(other.m[2][2], self.m[2][3] * other.m[3][2]),
863 ),
864 ),
865 self.m[2][0].mul_add(
866 other.m[0][3],
867 self.m[2][1].mul_add(
868 other.m[1][3],
869 self.m[2][2].mul_add(other.m[2][3], self.m[2][3] * other.m[3][3]),
870 ),
871 ),
872 self.m[3][0].mul_add(
873 other.m[0][0],
874 self.m[3][1].mul_add(
875 other.m[1][0],
876 self.m[3][2].mul_add(other.m[2][0], self.m[3][3] * other.m[3][0]),
877 ),
878 ),
879 self.m[3][0].mul_add(
880 other.m[0][1],
881 self.m[3][1].mul_add(
882 other.m[1][1],
883 self.m[3][2].mul_add(other.m[2][1], self.m[3][3] * other.m[3][1]),
884 ),
885 ),
886 self.m[3][0].mul_add(
887 other.m[0][2],
888 self.m[3][1].mul_add(
889 other.m[1][2],
890 self.m[3][2].mul_add(other.m[2][2], self.m[3][3] * other.m[3][2]),
891 ),
892 ),
893 self.m[3][0].mul_add(
894 other.m[0][3],
895 self.m[3][1].mul_add(
896 other.m[1][3],
897 self.m[3][2].mul_add(other.m[2][3], self.m[3][3] * other.m[3][3]),
898 ),
899 ),
900 )
901 }
902
903 #[cfg(target_arch = "x86_64")]
915 #[inline]
916 unsafe fn linear_combine_sse(a: [f32; 4], b: &Self) -> [f32; 4] {
917 unsafe {
918 use core::{
919 arch::x86_64::{__m128, _mm_add_ps, _mm_mul_ps, _mm_shuffle_ps},
920 mem,
921 };
922
923 let a: __m128 = mem::transmute(a);
924 let mut result = _mm_mul_ps(
925 _mm_shuffle_ps(a, a, 0x00),
926 mem::transmute::<[f32; 4], __m128>(b.m[0]),
927 );
928 result = _mm_add_ps(
929 result,
930 _mm_mul_ps(
931 _mm_shuffle_ps(a, a, 0x55),
932 mem::transmute::<[f32; 4], __m128>(b.m[1]),
933 ),
934 );
935 result = _mm_add_ps(
936 result,
937 _mm_mul_ps(
938 _mm_shuffle_ps(a, a, 0xaa),
939 mem::transmute::<[f32; 4], __m128>(b.m[2]),
940 ),
941 );
942 result = _mm_add_ps(
943 result,
944 _mm_mul_ps(
945 _mm_shuffle_ps(a, a, 0xff),
946 mem::transmute::<[f32; 4], __m128>(b.m[3]),
947 ),
948 );
949
950 mem::transmute(result)
951 }
952 }
953
954 #[cfg(target_arch = "x86_64")]
959 #[inline]
960 unsafe fn then_sse(&self, other: &Self) -> Self {
961 unsafe {
962 Self {
963 m: [
964 Self::linear_combine_sse(self.m[0], other),
965 Self::linear_combine_sse(self.m[1], other),
966 Self::linear_combine_sse(self.m[2], other),
967 Self::linear_combine_sse(self.m[3], other),
968 ],
969 }
970 }
971 }
972
973 #[cfg(target_arch = "x86_64")]
989 unsafe fn linear_combine_avx8(
990 a01: core::arch::x86_64::__m256,
991 b: &Self,
992 ) -> core::arch::x86_64::__m256 {
993 unsafe {
994 use core::arch::x86_64::{
995 _mm256_add_ps, _mm256_loadu2_m128, _mm256_mul_ps, _mm256_shuffle_ps,
996 };
997
998 let broadcast_row = |row: &[f32; 4]| {
1001 let p = row.as_ptr();
1002 _mm256_loadu2_m128(p, p)
1003 };
1004
1005 let mut result =
1006 _mm256_mul_ps(_mm256_shuffle_ps(a01, a01, 0x00), broadcast_row(&b.m[0]));
1007 result = _mm256_add_ps(
1008 result,
1009 _mm256_mul_ps(_mm256_shuffle_ps(a01, a01, 0x55), broadcast_row(&b.m[1])),
1010 );
1011 result = _mm256_add_ps(
1012 result,
1013 _mm256_mul_ps(_mm256_shuffle_ps(a01, a01, 0xaa), broadcast_row(&b.m[2])),
1014 );
1015 result = _mm256_add_ps(
1016 result,
1017 _mm256_mul_ps(_mm256_shuffle_ps(a01, a01, 0xff), broadcast_row(&b.m[3])),
1018 );
1019 result
1020 }
1021 }
1022
1023 #[cfg(target_arch = "x86_64")]
1031 #[inline]
1032 unsafe fn then_avx8(&self, other: &Self) -> Self {
1033 unsafe {
1034 use core::{
1035 arch::x86_64::{__m256, _mm256_loadu_ps, _mm256_storeu_ps, _mm256_zeroupper},
1036 mem,
1037 };
1038
1039 _mm256_zeroupper();
1040
1041 let a01: __m256 = _mm256_loadu_ps(&raw const self.m[0][0]);
1042 let a23: __m256 = _mm256_loadu_ps(&raw const self.m[2][0]);
1043
1044 let out01x = Self::linear_combine_avx8(a01, other);
1045 let out23x = Self::linear_combine_avx8(a23, other);
1046
1047 let mut out = Self {
1048 m: [self.m[0], self.m[1], self.m[2], self.m[3]],
1049 };
1050
1051 _mm256_storeu_ps(&raw mut out.m[0][0], out01x);
1052 _mm256_storeu_ps(&raw mut out.m[2][0], out23x);
1053
1054 out
1055 }
1056 }
1057
1058 #[must_use]
1060 #[inline]
1061 #[allow(clippy::suboptimal_flops)] fn make_rotation(
1063 rotation_origin: (f32, f32),
1064 mut degrees: f32,
1065 axis_x: f32,
1066 axis_y: f32,
1067 axis_z: f32,
1068 rotation_mode: RotationMode,
1070 ) -> Self {
1071 degrees = match rotation_mode {
1072 RotationMode::ForWebRender => -degrees,
1074 RotationMode::ForHitTesting => degrees,
1076 };
1077
1078 let (origin_x, origin_y) = rotation_origin;
1079 let pre_transform = Self::new_translation(-origin_x, -origin_y, 0.0);
1080 let post_transform = Self::new_translation(origin_x, origin_y, 0.0);
1081 let theta = 2.0_f32 * core::f32::consts::PI - degrees.to_radians();
1082 let rotate_transform = Self::new_rotation(axis_x, axis_y, axis_z, theta);
1083
1084 pre_transform.then(&rotate_transform).then(&post_transform)
1085 }
1086}
1087
1088#[cfg(test)]
1089#[allow(
1090 clippy::items_after_statements,
1091 clippy::redundant_clone,
1092 clippy::cast_possible_truncation,
1093 clippy::cast_sign_loss,
1094 trivial_casts,
1095 clippy::borrow_as_ptr,
1096 clippy::cast_ptr_alignment,
1097 clippy::unused_self,
1098 unused_qualifications,
1099 unreachable_pub,
1100 private_interfaces
1101)] mod audit_tests {
1103 use super::*;
1104
1105 fn sample_a() -> ComputedTransform3D {
1106 ComputedTransform3D::new(
1107 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
1108 )
1109 }
1110 fn sample_b() -> ComputedTransform3D {
1111 ComputedTransform3D::new(
1112 16.0, 15.0, 14.0, 13.0, 12.0, 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0,
1113 )
1114 }
1115
1116 fn approx_eq(a: &ComputedTransform3D, b: &ComputedTransform3D) {
1117 for r in 0..4 {
1118 for c in 0..4 {
1119 assert!(
1120 (a.m[r][c] - b.m[r][c]).abs() < 1e-3,
1121 "mismatch at [{r}][{c}]: {} vs {}",
1122 a.m[r][c],
1123 b.m[r][c]
1124 );
1125 }
1126 }
1127 }
1128
1129 fn naive_then(a: &ComputedTransform3D, b: &ComputedTransform3D) -> ComputedTransform3D {
1133 let mut out = ComputedTransform3D::IDENTITY;
1134 for r in 0..4 {
1135 for c in 0..4 {
1136 let mut acc = 0.0f32;
1137 for k in 0..4 {
1138 acc += a.m[r][k] * b.m[k][c];
1139 }
1140 out.m[r][c] = acc;
1141 }
1142 }
1143 out
1144 }
1145
1146 #[test]
1150 fn scalar_matmul_matches_reference() {
1151 let a = sample_a();
1152 let b = sample_b();
1153 approx_eq(&a.then(&b), &naive_then(&a, &b));
1154 approx_eq(&ComputedTransform3D::IDENTITY.then(&b), &b);
1156 approx_eq(&a.then(&ComputedTransform3D::IDENTITY), &a);
1157 }
1158
1159 #[cfg(not(miri))]
1168 #[test]
1169 fn simd_matmul_matches_scalar() {
1170 let a = sample_a();
1171 let b = sample_b();
1172 let scalar = a.then(&b);
1173
1174 #[cfg(target_arch = "x86_64")]
1175 {
1176 if std::is_x86_feature_detected!("sse") {
1177 let sse = unsafe { a.then_sse(&b) };
1178 approx_eq(&scalar, &sse);
1179 }
1180 if std::is_x86_feature_detected!("avx") {
1181 let avx = unsafe { a.then_avx8(&b) };
1182 approx_eq(&scalar, &avx);
1183 }
1184 }
1185
1186 approx_eq(&ComputedTransform3D::IDENTITY.then(&b), &b);
1188 }
1189
1190 #[cfg(all(target_arch = "x86_64", not(miri)))]
1201 #[test]
1202 fn avx_result_independent_of_row_alignment() {
1203 if !std::is_x86_feature_detected!("avx") {
1204 return;
1205 }
1206
1207 let a = sample_a();
1208 let b = sample_b();
1209 let expected = unsafe { a.then_avx8(&b) };
1210
1211 const N: usize = core::mem::size_of::<ComputedTransform3D>(); let mut buf = vec![0u8; N * 2 + 16];
1213 let base = buf.as_mut_ptr();
1214
1215 unsafe {
1220 let aligned = base.add(base.align_offset(16));
1221 let misaligned = aligned.add(4); for off_ptr in [aligned, misaligned] {
1223 core::ptr::copy_nonoverlapping((&raw const a).cast::<u8>(), off_ptr, N);
1224 let a_ref = &*off_ptr.cast::<ComputedTransform3D>();
1225 let got = a_ref.then_avx8(&b);
1226 approx_eq(&expected, &got);
1227 }
1228 }
1229 }
1230}
1231
1232#[cfg(test)]
1233#[path = "transform_test.rs"]
1234mod transform_test;