This one is a bit tricky. The tl;dr; is that switching from right-hand
multiplication to left-hand multiplication required applying the stack
from left to root. This actually allowed simplifying the code a bit,
since CoglMatrixEntry only stores a pointer to its parent, and that's
all we need to know for left-hand multiplication.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
In this case, since we are building the entire matrix by ourselves,
reverse the order of operations (translate + scale → scale + translate)
and build it using graphene-specific APIs.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
This is another instance of graphene reversing the order of operations (see
the commit notes of how ClutterActor was ported.) The tl;dr; here is that,
in the CoglMatrix past, we used to do:
(actor transforms) → scale
and now, it's the other way round:
scale → (actor transforms)
due to changing from right-handed multiplications (CoglMatrix) to left-handed
ones (graphene_matrix_t).
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
ClutterActor is a particularly heavy user of matrices, and
switching to graphene_matrix_* APIs means we had to change
the order of operations due to left-hand vs right-hand
differences.
When applying the actor transform, there are 2 main branches
that can be followed: the default transforms, and when a
custom transform is set.
To facilitate review, here's the table that I've made to
guide myself:
+--------------- Case 1: Default Transforms --------------+
| CoglMatrix | graphene_matrix_t |
+----------------------------+----------------------------+
| multiply (child transform) | translate (-pivot) |
| translate (allocation)¹ | rotate_x (angle) |
| translate (pivot)¹ | rotate_y (angle) |
| translate (translation)¹ | rotate_z (angle) |
| scale (sx, sy, sz) | scale (sx, sy, sz) |
| rotate_z (angle) | translate (translation)¹ |
| rotate_y (angle) | translate (pivot)¹ |
| rotate_x (angle) | translate (allocation)¹ |
| translate (-pivot) | multiply (child transform) |
+----------------------------+----------------------------+
¹ - these 3 translations are simplified as a single call
to translate(allocation + pivot + translation)
+---------------- Case 2: Custom Transform ---------------+
| CoglMatrix | graphene_matrix_t |
+----------------------------+----------------------------+
| multiply (child transform) | translate (-pivot) |
| translate (allocation)² | multiply (transform) |
| translate (pivot)² | translate (pivot)² |
| multiply (transform) | translate (allocation)² |
| translate (-pivot) | multiply (child transform) |
+----------------------------+----------------------------+
² - likewise, these 2 translations are simplified as a
single call to translate(allocation + pivot)
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
CoglMatrix already is a typedef to graphene_matrix_t. This commit
simply drops the CoglMatrix type, and align parameters. There is
no functional change here, it's simply a find-and-replace commit.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Ideally, we would use Graphene to do that, however as of now Graphene
lacks these APIs so we still need these helpers. Since we're preparing
to get rid of CoglMatrix, move them to a separate file, and rename them
with the 'cogl_graphene' prefix.
Since I'm already touching the world with this change, I'm also renaming
cogl_matrix_transform_point() to cogl_graphene_matrix_project_point(),
as per XXX comment, to make it consistent with the transform/projection
semantics in place.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Given that CoglMatrix is simply a typedef to graphene_matrix_t, we can
remove all the GType machinery and reuse Graphene's.
Also remove the clutter-cogl helper, and cogl_matrix_to_graphene_matrix()
which is now unused.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
After the previous commit, the only field in the CoglMatrix structure is
a graphene_matrix_t. That means that CoglMatrix is effectively a graphene
matrix now, and the CoglMatrix struct isn't that much useful anymore.
Remove the CoglMatrix structure and make the CoglMatrix type a typedef to
graphene_matrix_t.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Remove the cached inverse, and dirty flags, and typedef CoglMatrix to
graphene_matrix_t itself. I preverved the type for this commit to help
reducing the commit size, next commits will remove the CoglMatrix type.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
It turns it to be quite easy to inverse the transform, and doing that
on ClutterActor level means we can actually think about removing
CoglMatrix entirely and using graphene_matrix_t everywhere.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
CoglMatrix doesn't have a 1:1 mapping of graphene functions, and
sometimes it's just not worth adding wrappers over it. It is easier
to expose the internal graphene_matrix_t and let callers use it
directly.
Add new cogl_matrix_get_graphene_matrix() helper function, and
simplify Clutter's matrix progress function.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Instead of listing all matrix cells as floats, and the inverse
as a 16-length float array, use graphene_matrix_t in the structure
itself.
With this commit, all from/to CoglMatrix conversions are gone. It
is also not possible to initialize a CoglMatrix using the macro
anymore.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Rename cogl_matrix_get_array() to cogl_matrix_to_float(), and
make it copy the floats to an out argument instead of returning
a pointer to the casted CoglMatrix struct.
The naming change is specifically made to match graphene's,
and ease the transition.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Internally, a graphene_matrix_t representing the same transform that
of a CoglMatrix is the same matrix but transposed, so in order to get
the same element given a column and row for a matrix as if it would
be located in Cogl, it is necessary to swap the row and column when
retrieving it from the graphene matrix.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Turns out inverting a matrix was the largest chunk of the CoglMatrix
code. By switching to Graphene, a lot of it can go away. The inverse
is still cached in the CoglMatrix struct itself, to preserve the
optimization.
However, switching to graphene_matrix_t to calculate the inverse has
a challenge: float precision. We had to work around it here, and it
needs an explanation.
The way to detect whether a matrix is invertible or not (i.e.
whether it's not a "singular" matrix, or not) is by checking
if the determinant equals 0. So far, so good.
Both graphene_matrix_t and CoglMatrix use single-precision
floats to store their 4x4 matrices. Graphene uses vectorized
operations to optimize determinant calculation, while Cogl
tries to keep track of the matrix type and has special-purpose
determinant functions for different matrix types (the most
common one being a 3D matrix).
Cogl, however, has a fundamentally flawed check for whether
the matrix is invertible or not. Have a look:
```
float det;
…
if (det*det < 1e-25)
return FALSE;
```
Notice that 1e-25 is *way* smaller than FLT_EPSILON. This
check is fundamentally flawed.
"In practice, what does it break?", the reader might ask.
Well, in this case, the answer is opposite of that: Cogl
inverts matrices that should not be invertible. Let's see
an example: the model-view-projection of a 4K monitor. It
looks like this:
```
| +0,002693 +0,000000 +0,000000 +0,000000 |
| +0,000000 -0,002693 +0,000000 +0,000000 |
| +0,000000 +0,000000 +0,002693 +0,000000 |
| -5,169809 +2,908017 -5,036834 +1,000000 |
```
The determinant of this matrix is -0.000000019530306557.
It evidently is smaller than FLT_EPSILON. In this situation,
Cogl would happily calculate the inverse matrix, whereas
Graphene (correctly) bails out and thinks it's a singular
matrix.
This commit works around that by exploiting the maths around
it. The basis of it is:
inverse(scalar * M) = (1/scalar) * M'
which can be extrapolated to:
inverse(M) = scalar * inverse(scalar * M) = M'
In other words, scaling the to-be-inversed matrix, then
scaling the inverse matrix by the same factor, gives us
the desired inverse. In this commit, the scale is calculated
as 1 / (smallest value in the diagonal).
I'm sorry for everyone that has to read through this :(
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Next commits, and this patchset in general, will make this patchset
obsolete, since it'll only test graphene types against each other.
If at all useful, the Euler test should be moved to graphene.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
Use dot products to simplify calculations. Because the 'w' column of
the matrix is always summed, use 1.f in the 'w' component of the point
vector.
Because CoglMatrix is column-major and graphene_matrix_t is row-major,
it is necessary to transpose the matrix before retrieving the rows.
When we switch CoglMatrix to be row-major, this transposition will
go away.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
This is pretty similar to the other conversions, except we need to
store the matrix flags before operating on it, and update it using
this old value after. That's because cogl_matrix_init_from_array()
marks the matrix as entirely dirty, and we don't want that.
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439
At this point, we are still only changing CoglMatrix APIs internally, and
it should still produce the same output as before.
To achieve this, using graphens matrix implementation, we need to exploit
some knowledge about conventions used in Cogl and graphene respectively.
In Cogl, transformation matrices are equivalent to those of affine
transformation matrices. The convention used by graphene, however, is to
operate on matrices that are transposed compared to their affine
counterparts.
So for example, let's say we want to multiply the affine matrices A and B,
to get C.
A × B = C
The first step is to convert A and B to graphene matrices. We do this by
importing the floating point array, importing it directly using graphene.
Cogl exports its matrix to a column major floating point array. When we
import this in graphene, being row major, we end up with the same matrix,
only transposed.
Cogl Graphene
A <===> Aᵀ
B <===> Bᵀ
We then multiply these imported matrices in reverse
Bᵀ × Aᵀ
which in turn, due to ABᵀ = BᵀAᵀ, gives us
Bᵀ × Aᵀ = (A × B)ᵀ
Our original goal was to find C, thus we know that
A × B = C
That means we can shuffle things around a bit.
A × B = C
Bᵀ × Aᵀ = (A × B)ᵀ
Bᵀ × Aᵀ = Cᵀ
With the same conversion as done when going from Cogl to graphene, only
the other way around, we still end up effectively transposing the matrix
during the conversion.
Graphene Cogl
Cᵀ <===> C
Thus when converting Cᵀ to Cogl, we in fact end up with C.
(Explanation authored by Jonas Ådahl)
https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1439