From 92a9860e1d3e601e440e349508ff4ec864b95b65 Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Thu, 27 Jan 2022 11:20:34 -0600 Subject: [PATCH] libweston: Add function to find the output transform of a matrix When we build up a matrix from a series of operations, it's very useful to know if the combined operations still result in something that matches a wl_output_transform. This adds a function to test if a matrix leads to a standard output transform, and returns the transform if it does. Tests are provided that check if complex series of operations return expected results - the weston_matrix_needs_filtering function is tested at the same time. Signed-off-by: Derek Foreman --- include/libweston/matrix.h | 6 + shared/matrix.c | 121 ++++++++++++++ tests/matrix-transform-test.c | 286 ++++++++++++++++++++++++++++++++++ tests/meson.build | 4 + 4 files changed, 417 insertions(+) create mode 100644 tests/matrix-transform-test.c diff --git a/include/libweston/matrix.h b/include/libweston/matrix.h index a05e31a8e..083100032 100644 --- a/include/libweston/matrix.h +++ b/include/libweston/matrix.h @@ -29,6 +29,8 @@ #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -71,6 +73,10 @@ weston_matrix_invert(struct weston_matrix *inverse, bool weston_matrix_needs_filtering(const struct weston_matrix *matrix); +bool +weston_matrix_to_transform(const struct weston_matrix *mat, + enum wl_output_transform *transform); + #ifdef __cplusplus } #endif diff --git a/shared/matrix.c b/shared/matrix.c index 369765b21..f301c5fac 100644 --- a/shared/matrix.c +++ b/shared/matrix.c @@ -296,6 +296,12 @@ near_zero_at(const struct weston_matrix *matrix, int row, int col) return near_zero(get_el(matrix, row, col)); } +static bool +near_one_at(const struct weston_matrix *matrix, int row, int col) +{ + return near_zero(get_el(matrix, row, col) - 1.0); +} + static bool near_pm_one_at(const struct weston_matrix *matrix, int row, int col) { @@ -395,3 +401,118 @@ weston_matrix_needs_filtering(const struct weston_matrix *matrix) * heuristics, so recommend filtering */ return true; } + +/** Examine a matrix to see if it applies a standard output transform. + * + * \param mat matrix to examine + * \param[out] transform the transform, if applicable + * \return true if a standard transform is present + + * Note that the check only considers rotations and flips. + * If any other scale or translation is present, those may have to + * be dealt with by the caller in some way. + */ +WL_EXPORT bool +weston_matrix_to_transform(const struct weston_matrix *mat, + enum wl_output_transform *transform) +{ + /* As a first pass we can eliminate any matrix that doesn't have + * zeroes in these positions: + * [ ? ? 0 ? ] + * [ ? ? 0 ? ] + * [ 0 0 ? ? ] + * [ 0 0 0 ? ] + * As they will be non-affine, or rotations about axes + * other than Z. + */ + if (!near_zero_at(mat, 2, 0) || + !near_zero_at(mat, 3, 0) || + !near_zero_at(mat, 2, 1) || + !near_zero_at(mat, 3, 1) || + !near_zero_at(mat, 0, 2) || + !near_zero_at(mat, 1, 2) || + !near_zero_at(mat, 3, 2)) + return false; + + /* Enforce the form: + * [ ? ? 0 ? ] + * [ ? ? 0 ? ] + * [ 0 0 ? ? ] + * [ 0 0 0 1 ] + * While we could scale all the elements by a constant to make + * 3,3 == 1, we choose to be lazy and not bother. A matrix + * that doesn't fit this form seems likely to be too complicated + * to pass the other checks. + */ + if (!near_one_at(mat, 3, 3)) + return false; + + if (near_zero_at(mat, 0, 0)) { + if (!near_zero_at(mat, 1, 1)) + return false; + + /* We now have a matrix like: + * [ 0 A 0 ? ] + * [ B 0 0 ? ] + * [ 0 0 ? ? ] + * [ 0 0 0 1 ] + * When transforming a vector with a matrix of this form, the X + * and Y coordinates are effectively exchanged, so we have a + * 90 or 270 degree rotation (not 0 or 180), and could have + * a flip depending on the signs of A and B. + * + * We don't require A and B to have the same absolute value, + * so there may be independent scales in the X or Y dimensions. + */ + if (get_el(mat, 0, 1) > 0) { + /* A is positive */ + + if (get_el(mat, 1, 0) > 0) + *transform = WL_OUTPUT_TRANSFORM_FLIPPED_90; + else + *transform = WL_OUTPUT_TRANSFORM_90; + } else { + /* A is negative */ + + if (get_el(mat, 1, 0) > 0) + *transform = WL_OUTPUT_TRANSFORM_270; + else + *transform = WL_OUTPUT_TRANSFORM_FLIPPED_270; + } + } else if (near_zero_at(mat, 1, 0)) { + if (!near_zero_at(mat, 0, 1)) + return false; + + /* We now have a matrix like: + * [ A 0 0 ? ] + * [ 0 B 0 ? ] + * [ 0 0 ? ? ] + * [ 0 0 0 1 ] + * This case won't exchange the X and Y inputs, so the + * transform is 0 or 180 degrees. We could have a flip + * depending on the signs of A and B. + * + * We don't require A and B to have the same absolute value, + * so there may be independent scales in the X or Y dimensions. + */ + if (get_el(mat, 0, 0) > 0) { + /* A is positive */ + + if (get_el(mat, 1, 1) > 0) + *transform = WL_OUTPUT_TRANSFORM_NORMAL; + else + *transform = WL_OUTPUT_TRANSFORM_FLIPPED_180; + } else { + /* A is negative */ + + if (get_el(mat, 1, 1) > 0) + *transform = WL_OUTPUT_TRANSFORM_FLIPPED; + else + *transform = WL_OUTPUT_TRANSFORM_180; + } + } else { + return false; + } + + return true; +} diff --git a/tests/matrix-transform-test.c b/tests/matrix-transform-test.c new file mode 100644 index 000000000..a5c995282 --- /dev/null +++ b/tests/matrix-transform-test.c @@ -0,0 +1,286 @@ +/* + * Copyright © 2022 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "libweston-internal.h" +#include "libweston/matrix.h" + +#include "weston-test-client-helper.h" + +static void +transform_expect(struct weston_matrix *a, bool valid, enum wl_output_transform ewt) +{ + enum wl_output_transform wt; + + assert(weston_matrix_to_transform(a, &wt) == valid); + if (valid) + assert(wt == ewt); +} + +TEST(transformation_matrix) +{ + struct weston_matrix a, b; + int i; + + weston_matrix_init(&a); + weston_matrix_init(&b); + + weston_matrix_multiply(&a, &b); + assert(a.type == 0); + + /* Make b a matrix that rotates a surface on the x,y plane by 90 + * degrees counter-clockwise */ + weston_matrix_rotate_xy(&b, 0, -1); + assert(b.type == WESTON_MATRIX_TRANSFORM_ROTATE); + for (i = 0; i < 10; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_90); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_180); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_270); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + } + + weston_matrix_init(&b); + /* Make b a matrix that rotates a surface on the x,y plane by 45 + * degrees counter-clockwise. This should alternate between a + * standard transform and a rotation that fails to match any + * known rotations. */ + weston_matrix_rotate_xy(&b, cos(-M_PI / 4.0), sin(-M_PI / 4.0)); + assert(b.type == WESTON_MATRIX_TRANSFORM_ROTATE); + for (i = 0; i < 10; i++) { + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_90); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_180); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_270); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + } + + weston_matrix_init(&b); + /* Make b a matrix that rotates a surface on the x,y plane by 45 + * degrees counter-clockwise. This should alternate between a + * standard transform and a rotation that fails to match any known + * rotations. */ + weston_matrix_rotate_xy(&b, cos(-M_PI / 4.0), sin(-M_PI / 4.0)); + /* Flip a */ + weston_matrix_scale(&a, -1.0, 1.0, 1.0); + for (i = 0; i < 10; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + /* Since we're not translated or scaled, any matrix that + * matches a standard wl_output_transform should not need + * filtering when used to transform images - but any + * matrix that fails to match will. */ + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_90); + assert(!weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_180); + assert(!weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_270); + assert(!weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED); + assert(!weston_matrix_needs_filtering(&a)); + } + + weston_matrix_init(&a); + /* Flip a around Y*/ + weston_matrix_scale(&a, 1.0, -1.0, 1.0); + for (i = 0; i < 100; i++) { + /* Throw some arbitrary translation in here to make sure it + * doesn't have any impact. */ + weston_matrix_translate(&a, 31.0, -25.0, 0.0); + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_270); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_90); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_180); + } + + /* Scale shouldn't matter, as long as it's positive */ + weston_matrix_scale(&a, 4.0, 3.0, 1.0); + /* Invert b so it rotates the opposite direction, go back the other way. */ + weston_matrix_invert(&b, &b); + for (i = 0; i < 100; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_90); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_270); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_180); + assert(weston_matrix_needs_filtering(&a)); + } + + /* Flipping Y should return us from here to normal */ + weston_matrix_scale(&a, 1.0, -1.0, 1.0); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + + weston_matrix_init(&a); + weston_matrix_init(&b); + weston_matrix_translate(&b, 0.5, -0.75, 0); + /* Crawl along with translations, 0.5 and .75 will both hit an integer multiple + * at the same time every 4th step, so assert that only the 4th steps don't need + * filtering */ + for (i = 0; i < 100; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(!weston_matrix_needs_filtering(&a)); + } + + weston_matrix_init(&b); + weston_matrix_scale(&b, 1.5, 2.0, 1.0); + for (i = 0; i < 10; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + } + weston_matrix_invert(&b, &b); + for (i = 0; i < 9; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + } + /* Last step should bring us back to a matrix that doesn't need + * a filter */ + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(!weston_matrix_needs_filtering(&a)); +} diff --git a/tests/meson.build b/tests/meson.build index cc35934dd..6557f8bab 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -176,6 +176,10 @@ tests = [ 'name': 'matrix', 'dep_objs': [ dep_libm ] }, + { + 'name': 'matrix-transform', + 'dep_objs': dep_libm, + }, { 'name': 'output-damage', }, { 'name': 'output-transforms', }, { 'name': 'plugin-registry', },