1
0
Fork 0
mutter-performance-source/src/compositor/region-utils.c
msizanoen1 8b3c1f4b87 cullable: Generalize actor coordinates space translation for regions
This allows MetaCullable to work with actors using arbitrary transforms
which will be needed for implementing surface pixel alignment for
fractional-scale-v1.

This also deletes meta_cullable_is_untransformed as it's no longer
necessary, and we can also stop manually scaling the region objects
while performing opaque region culling in surfaces since it's now
handled transparently by the new `meta_cullable_cull_out_children`
implementation.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2726>
2023-05-22 14:44:33 +00:00

507 lines
16 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Utilities for region manipulation
*
* Copyright (C) 2010 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "backends/meta-monitor-transform.h"
#include "compositor/region-utils.h"
#include "core/boxes-private.h"
#include <math.h>
#define META_REGION_MAX_STACK_RECTS 256
#define META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED(n_rects, rects) \
g_autofree cairo_rectangle_int_t *G_PASTE(__n, __LINE__) = NULL; \
if (n_rects < META_REGION_MAX_STACK_RECTS) \
rects = g_newa (cairo_rectangle_int_t, n_rects); \
else \
rects = G_PASTE(__n, __LINE__) = g_new (cairo_rectangle_int_t, n_rects);
/* MetaRegionBuilder */
/* Various algorithms in this file require unioning together a set of rectangles
* that are unsorted or overlap; unioning such a set of rectangles 1-by-1
* using cairo_region_union_rectangle() produces O(N^2) behavior (if the union
* adds or removes rectangles in the middle of the region, then it has to
* move all the rectangles after that.) To avoid this behavior, MetaRegionBuilder
* creates regions for small groups of rectangles and merges them together in
* a binary tree.
*
* Possible improvement: From a glance at the code, accumulating all the rectangles
* into a flat array and then calling the (not usefully documented)
* cairo_region_create_rectangles() would have the same behavior and would be
* simpler and a bit more efficient.
*/
/* Optimium performance seems to be with MAX_CHUNK_RECTANGLES=4; 8 is about 10% slower.
* But using 8 may be more robust to systems with slow malloc(). */
#define MAX_CHUNK_RECTANGLES 8
void
meta_region_builder_init (MetaRegionBuilder *builder)
{
int i;
for (i = 0; i < META_REGION_BUILDER_MAX_LEVELS; i++)
builder->levels[i] = NULL;
builder->n_levels = 1;
}
void
meta_region_builder_add_rectangle (MetaRegionBuilder *builder,
int x,
int y,
int width,
int height)
{
cairo_rectangle_int_t rect;
int i;
if (builder->levels[0] == NULL)
builder->levels[0] = cairo_region_create ();
rect.x = x;
rect.y = y;
rect.width = width;
rect.height = height;
cairo_region_union_rectangle (builder->levels[0], &rect);
if (cairo_region_num_rectangles (builder->levels[0]) >= MAX_CHUNK_RECTANGLES)
{
for (i = 1; i < builder->n_levels + 1; i++)
{
if (builder->levels[i] == NULL)
{
if (i < META_REGION_BUILDER_MAX_LEVELS)
{
builder->levels[i] = builder->levels[i - 1];
builder->levels[i - 1] = NULL;
if (i == builder->n_levels)
builder->n_levels++;
}
break;
}
else
{
cairo_region_union (builder->levels[i], builder->levels[i - 1]);
cairo_region_destroy (builder->levels[i - 1]);
builder->levels[i - 1] = NULL;
}
}
}
}
cairo_region_t *
meta_region_builder_finish (MetaRegionBuilder *builder)
{
cairo_region_t *result = NULL;
int i;
for (i = 0; i < builder->n_levels; i++)
{
if (builder->levels[i])
{
if (result == NULL)
result = builder->levels[i];
else
{
cairo_region_union(result, builder->levels[i]);
cairo_region_destroy (builder->levels[i]);
}
}
}
if (result == NULL)
result = cairo_region_create ();
return result;
}
/* MetaRegionIterator */
void
meta_region_iterator_init (MetaRegionIterator *iter,
cairo_region_t *region)
{
iter->region = region;
iter->i = 0;
iter->n_rectangles = cairo_region_num_rectangles (region);
iter->line_start = TRUE;
if (iter->n_rectangles > 1)
{
cairo_region_get_rectangle (region, 0, &iter->rectangle);
cairo_region_get_rectangle (region, 1, &iter->next_rectangle);
iter->line_end = iter->next_rectangle.y != iter->rectangle.y;
}
else if (iter->n_rectangles > 0)
{
cairo_region_get_rectangle (region, 0, &iter->rectangle);
iter->line_end = TRUE;
}
}
gboolean
meta_region_iterator_at_end (MetaRegionIterator *iter)
{
return iter->i >= iter->n_rectangles;
}
void
meta_region_iterator_next (MetaRegionIterator *iter)
{
iter->i++;
iter->rectangle = iter->next_rectangle;
iter->line_start = iter->line_end;
if (iter->i + 1 < iter->n_rectangles)
{
cairo_region_get_rectangle (iter->region, iter->i + 1, &iter->next_rectangle);
iter->line_end = iter->next_rectangle.y != iter->rectangle.y;
}
else
{
iter->line_end = TRUE;
}
}
cairo_region_t *
meta_region_scale_double (cairo_region_t *region,
double scale,
MetaRoundingStrategy rounding_strategy)
{
int n_rects, i;
cairo_rectangle_int_t *rects;
cairo_region_t *scaled_region;
g_return_val_if_fail (scale > 0.0, NULL);
if (G_APPROX_VALUE (scale, 1.f, FLT_EPSILON))
return cairo_region_copy (region);
n_rects = cairo_region_num_rectangles (region);
META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects);
for (i = 0; i < n_rects; i++)
{
cairo_region_get_rectangle (region, i, &rects[i]);
meta_rectangle_scale_double (&rects[i], scale, rounding_strategy,
&rects[i]);
}
scaled_region = cairo_region_create_rectangles (rects, n_rects);
return scaled_region;
}
cairo_region_t *
meta_region_scale (cairo_region_t *region, int scale)
{
int n_rects, i;
cairo_rectangle_int_t *rects;
cairo_region_t *scaled_region;
if (scale == 1)
return cairo_region_copy (region);
n_rects = cairo_region_num_rectangles (region);
META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects);
for (i = 0; i < n_rects; i++)
{
cairo_region_get_rectangle (region, i, &rects[i]);
rects[i].x *= scale;
rects[i].y *= scale;
rects[i].width *= scale;
rects[i].height *= scale;
}
scaled_region = cairo_region_create_rectangles (rects, n_rects);
return scaled_region;
}
static void
add_expanded_rect (MetaRegionBuilder *builder,
int x,
int y,
int width,
int height,
int x_amount,
int y_amount,
gboolean flip)
{
if (flip)
meta_region_builder_add_rectangle (builder,
y - y_amount, x - x_amount,
height + 2 * y_amount, width + 2 * x_amount);
else
meta_region_builder_add_rectangle (builder,
x - x_amount, y - y_amount,
width + 2 * x_amount, height + 2 * y_amount);
}
static cairo_region_t *
expand_region (cairo_region_t *region,
int x_amount,
int y_amount,
gboolean flip)
{
MetaRegionBuilder builder;
int n;
int i;
meta_region_builder_init (&builder);
n = cairo_region_num_rectangles (region);
for (i = 0; i < n; i++)
{
cairo_rectangle_int_t rect;
cairo_region_get_rectangle (region, i, &rect);
add_expanded_rect (&builder,
rect.x, rect.y, rect.width, rect.height,
x_amount, y_amount, flip);
}
return meta_region_builder_finish (&builder);
}
/* This computes a (clipped version) of the inverse of the region
* and expands it by the given amount */
static cairo_region_t *
expand_region_inverse (cairo_region_t *region,
int x_amount,
int y_amount,
gboolean flip)
{
MetaRegionBuilder builder;
MetaRegionIterator iter;
cairo_rectangle_int_t extents;
int last_x;
meta_region_builder_init (&builder);
cairo_region_get_extents (region, &extents);
add_expanded_rect (&builder,
extents.x, extents.y - 1, extents.width, 1,
x_amount, y_amount, flip);
add_expanded_rect (&builder,
extents.x - 1, extents.y, 1, extents.height,
x_amount, y_amount, flip);
add_expanded_rect (&builder,
extents.x + extents.width, extents.y, 1, extents.height,
x_amount, y_amount, flip);
add_expanded_rect (&builder,
extents.x, extents.y + extents.height, extents.width, 1,
x_amount, y_amount, flip);
last_x = extents.x;
for (meta_region_iterator_init (&iter, region);
!meta_region_iterator_at_end (&iter);
meta_region_iterator_next (&iter))
{
if (iter.rectangle.x > last_x)
add_expanded_rect (&builder,
last_x, iter.rectangle.y,
iter.rectangle.x - last_x, iter.rectangle.height,
x_amount, y_amount, flip);
if (iter.line_end)
{
if (extents.x + extents.width > iter.rectangle.x + iter.rectangle.width)
add_expanded_rect (&builder,
iter.rectangle.x + iter.rectangle.width, iter.rectangle.y,
(extents.x + extents.width) - (iter.rectangle.x + iter.rectangle.width), iter.rectangle.height,
x_amount, y_amount, flip);
last_x = extents.x;
}
else
last_x = iter.rectangle.x + iter.rectangle.width;
}
return meta_region_builder_finish (&builder);
}
/**
* meta_make_border_region:
* @region: a #cairo_region_t
* @x_amount: distance from the border to extend horizontally
* @y_amount: distance from the border to extend vertically
* @flip: if true, the result is computed with x and y interchanged
*
* Computes the "border region" of a given region, which is roughly
* speaking the set of points near the boundary of the region. If we
* define the operation of growing a region as computing the set of
* points within a given manhattan distance of the region, then the
* border is 'grow(region) intersect grow(inverse(region))'.
*
* If we create an image by filling the region with a solid color,
* the border is the region affected by blurring the region.
*
* Return value: a new region which is the border of the given region
*/
cairo_region_t *
meta_make_border_region (cairo_region_t *region,
int x_amount,
int y_amount,
gboolean flip)
{
cairo_region_t *border_region;
cairo_region_t *inverse_region;
border_region = expand_region (region, x_amount, y_amount, flip);
inverse_region = expand_region_inverse (region, x_amount, y_amount, flip);
cairo_region_intersect (border_region, inverse_region);
cairo_region_destroy (inverse_region);
return border_region;
}
cairo_region_t *
meta_region_transform (const cairo_region_t *region,
MetaMonitorTransform transform,
int width,
int height)
{
int n_rects, i;
cairo_rectangle_int_t *rects;
cairo_region_t *transformed_region;
if (transform == META_MONITOR_TRANSFORM_NORMAL)
return cairo_region_copy (region);
n_rects = cairo_region_num_rectangles (region);
META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects);
for (i = 0; i < n_rects; i++)
{
cairo_region_get_rectangle (region, i, &rects[i]);
meta_rectangle_transform (&rects[i],
transform,
width,
height,
&rects[i]);
}
transformed_region = cairo_region_create_rectangles (rects, n_rects);
return transformed_region;
}
cairo_region_t *
meta_region_crop_and_scale (cairo_region_t *region,
graphene_rect_t *src_rect,
int dst_width,
int dst_height)
{
int n_rects, i;
cairo_rectangle_int_t *rects;
cairo_region_t *viewport_region;
if (G_APPROX_VALUE (src_rect->size.width, dst_width, FLT_EPSILON) &&
G_APPROX_VALUE (src_rect->size.height, dst_height, FLT_EPSILON) &&
G_APPROX_VALUE (roundf (src_rect->origin.x),
src_rect->origin.x, FLT_EPSILON) &&
G_APPROX_VALUE (roundf (src_rect->origin.y),
src_rect->origin.y, FLT_EPSILON))
{
viewport_region = cairo_region_copy (region);
if (!G_APPROX_VALUE (src_rect->origin.x, 0, FLT_EPSILON) ||
!G_APPROX_VALUE (src_rect->origin.y, 0, FLT_EPSILON))
{
cairo_region_translate (viewport_region,
(int) src_rect->origin.x,
(int) src_rect->origin.y);
}
return viewport_region;
}
n_rects = cairo_region_num_rectangles (region);
META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects);
for (i = 0; i < n_rects; i++)
{
cairo_region_get_rectangle (region, i, &rects[i]);
meta_rectangle_crop_and_scale (&rects[i],
src_rect,
dst_width,
dst_height,
&rects[i]);
}
viewport_region = cairo_region_create_rectangles (rects, n_rects);
return viewport_region;
}
void
meta_region_to_cairo_path (cairo_region_t *region,
cairo_t *cr)
{
cairo_rectangle_int_t rect;
int n_rects, i;
n_rects = cairo_region_num_rectangles (region);
for (i = 0; i < n_rects; i++)
{
cairo_region_get_rectangle (region, i, &rect);
cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height);
}
}
cairo_region_t *
meta_region_apply_matrix_transform_expand (const cairo_region_t *region,
graphene_matrix_t *transform)
{
int n_rects, i;
cairo_rectangle_int_t *rects;
cairo_region_t *transformed_region;
if (graphene_matrix_is_identity (transform))
return cairo_region_copy (region);
n_rects = cairo_region_num_rectangles (region);
META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects);
for (i = 0; i < n_rects; i++)
{
graphene_rect_t transformed_rect, rect;
cairo_rectangle_int_t int_rect;
cairo_region_get_rectangle (region, i, &int_rect);
rect = meta_rectangle_to_graphene_rect (&int_rect);
graphene_matrix_transform_bounds (transform, &rect, &transformed_rect);
meta_rectangle_from_graphene_rect (&transformed_rect,
META_ROUNDING_STRATEGY_GROW,
&rects[i]);
}
transformed_region = cairo_region_create_rectangles (rects, n_rects);
return transformed_region;
}