compositor: new intersection algorithm

The existing algorithm had some corner cases (pun!), where it failed to
produce correct vertices in the right order. This appeared only when the
surface was transformed (rotated). It also produced degenerate polygons
(3 or more vertices with zero polygon area) for non-transformed cases
where the clipping and surface rectangles were adjacent but not
overlapping.

Introduce a new algorithm for finding the boundary vertices of the
intersection of a coordinate axis aligned rectangle and an arbitrary
polygon (here a quadrilateral). The code is based on the
Sutherland-Hodgman algorithm, where a polygon is clipped by infinite
lines one at a time.

This new algorithm should always produce the correct vertices in the
clockwise winding order, and discard duplicate vertices and degenerate
polygons. It retains the fast paths of the existing algorithm for the
no-hit and non-transformed cases.

Benchmarking with earlier versions showed that the new algorithm is
a little slower (56 vs. 68 us/call) than the existing algorithm, for
the transformed case.  The 'cliptest f' command before and after this
commit can be used to compare the speed of the transformed case only.

Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
Acked-by: Rob Clark <rob.clark@linaro.org>
dev
Pekka Paalanen 13 years ago committed by Kristian Høgsberg
parent 8c492b1293
commit 0d64a0fa29
  1. 546
      clients/cliptest.c
  2. 547
      src/gles2-renderer.c

@ -81,294 +81,368 @@ weston_surface_to_global_float(struct weston_surface *surface,
/* ---------------------- copied begins -----------------------*/ /* ---------------------- copied begins -----------------------*/
#define max(a, b) (((a) > (b)) ? (a) : (b)) struct polygon8 {
#define min(a, b) (((a) > (b)) ? (b) : (a)) GLfloat x[8];
#define clip(x, a, b) min(max(x, a), b) GLfloat y[8];
#define sign(x) ((x) >= 0) int n;
};
static int struct clip_context {
calculate_edges(struct weston_surface *es, pixman_box32_t *rect, struct {
pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey) GLfloat x;
GLfloat y;
} prev;
struct {
GLfloat x1, y1;
GLfloat x2, y2;
} clip;
struct {
GLfloat *x;
GLfloat *y;
} vertices;
};
static GLfloat
float_difference(GLfloat a, GLfloat b)
{ {
int i, n = 0; /* http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ */
GLfloat min_x, max_x, min_y, max_y; static const GLfloat max_diff = 4.0f * FLT_MIN;
GLfloat x[4] = { static const GLfloat max_rel_diff = 4.0e-5;
surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1, GLfloat diff = a - b;
}; GLfloat adiff = fabsf(diff);
GLfloat y[4] = {
surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2,
};
GLfloat cx1 = rect->x1;
GLfloat cx2 = rect->x2;
GLfloat cy1 = rect->y1;
GLfloat cy2 = rect->y2;
GLfloat dist_squared(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
{
GLfloat dx = (x1 - x2);
GLfloat dy = (y1 - y2);
return dx * dx + dy * dy;
}
void append_vertex(GLfloat x, GLfloat y) if (adiff <= max_diff)
{ return 0.0f;
/* don't emit duplicate vertices: */
if ((n > 0) && (ex[n-1] == x) && (ey[n-1] == y))
return;
ex[n] = x;
ey[n] = y;
n++;
}
/* transform surface to screen space: */ a = fabsf(a);
for (i = 0; i < 4; i++) b = fabsf(b);
weston_surface_to_global_float(es, x[i], y[i], &x[i], &y[i]); if (adiff <= (a > b ? a : b) * max_rel_diff)
return 0.0f;
/* find bounding box: */ return diff;
min_x = max_x = x[0]; }
min_y = max_y = y[0];
for (i = 1; i < 4; i++) {
min_x = min(min_x, x[i]);
max_x = max(max_x, x[i]);
min_y = min(min_y, y[i]);
max_y = max(max_y, y[i]);
}
/* First, simple bounding box check to discard early transformed /* A line segment (p1x, p1y)-(p2x, p2y) intersects the line x = x_arg.
* surface rects that do not intersect with the clip region: * Compute the y coordinate of the intersection.
*/ */
if ((min_x > cx2) || (max_x < cx1) || static GLfloat
(min_y > cy2) || (max_y < cy1)) clip_intersect_y(GLfloat p1x, GLfloat p1y, GLfloat p2x, GLfloat p2y,
return 0; GLfloat x_arg)
{
GLfloat a;
GLfloat diff = float_difference(p1x, p2x);
/* Simple case, bounding box edges are parallel to surface edges, /* Practically vertical line segment, yet the end points have already
* there will be only four edges. We just need to clip the surface * been determined to be on different sides of the line. Therefore
* vertices to the clip rect bounds: * the line segment is part of the line and intersects everywhere.
* Return the end point, so we use the whole line segment.
*/ */
if (!es->transform.enabled) { if (diff == 0.0f)
for (i = 0; i < 4; i++) { return p2y;
ex[n] = clip(x[i], cx1, cx2);
ey[n] = clip(y[i], cy1, cy2);
n++;
}
return 4;
}
/* Hard case, transformation applied. We need to find the vertices a = (x_arg - p2x) / diff;
* of the shape that is the intersection of the clip rect and return p2y + (p1y - p2y) * a;
* transformed surface. This can be anything from 3 to 8 sides. }
*
* Observation: all the resulting vertices will be the intersection
* points of the transformed surface and the clip rect, plus the
* vertices of the clip rect which are enclosed by the transformed
* surface and the vertices of the transformed surface which are
* enclosed by the clip rect.
*
* Observation: there will be zero, one, or two resulting vertices
* for each edge of the src rect.
*
* Loop over four edges of the transformed rect:
*/
for (i = 0; i < 4; i++) {
GLfloat x1, y1, x2, y2;
int last_n = n;
x1 = x[i];
y1 = y[i];
/* if this vertex is contained in the clip rect, use it as-is: */
if ((cx1 <= x1) && (x1 <= cx2) &&
(cy1 <= y1) && (y1 <= cy2))
append_vertex(x1, y1);
/* for remaining, we consider the point as part of a line: */
x2 = x[(i+1) % 4];
y2 = y[(i+1) % 4];
if (x1 == x2) {
append_vertex(clip(x1, cx1, cx2), clip(y1, cy1, cy2));
append_vertex(clip(x2, cx1, cx2), clip(y2, cy1, cy2));
} else if (y1 == y2) {
append_vertex(clip(x1, cx1, cx2), clip(y1, cy1, cy2));
append_vertex(clip(x2, cx1, cx2), clip(y2, cy1, cy2));
} else {
GLfloat m, c, p;
GLfloat tx[2], ty[2];
int tn = 0;
int intersect_horiz(GLfloat y, GLfloat *p) /* A line segment (p1x, p1y)-(p2x, p2y) intersects the line y = y_arg.
{ * Compute the x coordinate of the intersection.
GLfloat x; */
static GLfloat
clip_intersect_x(GLfloat p1x, GLfloat p1y, GLfloat p2x, GLfloat p2y,
GLfloat y_arg)
{
GLfloat a;
GLfloat diff = float_difference(p1y, p2y);
/* if y does not lie between y1 and y2, no /* Practically horizontal line segment, yet the end points have already
* intersection possible * been determined to be on different sides of the line. Therefore
* the line segment is part of the line and intersects everywhere.
* Return the end point, so we use the whole line segment.
*/ */
if (sign(y-y1) == sign(y-y2)) if (diff == 0.0f)
return 0; return p2x;
x = (y - c) / m; a = (y_arg - p2y) / diff;
return p2x + (p1x - p2x) * a;
}
/* if x does not lie between cx1 and cx2, no enum path_transition {
* intersection: PATH_TRANSITION_OUT_TO_OUT = 0,
*/ PATH_TRANSITION_OUT_TO_IN = 1,
if (sign(x-cx1) == sign(x-cx2)) PATH_TRANSITION_IN_TO_OUT = 2,
return 0; PATH_TRANSITION_IN_TO_IN = 3,
};
*p = x; static void
return 1; clip_append_vertex(struct clip_context *ctx, GLfloat x, GLfloat y)
} {
*ctx->vertices.x++ = x;
*ctx->vertices.y++ = y;
}
int intersect_vert(GLfloat x, GLfloat *p) static enum path_transition
{ path_transition_left_edge(struct clip_context *ctx, GLfloat x, GLfloat y)
GLfloat y; {
return ((ctx->prev.x >= ctx->clip.x1) << 1) | (x >= ctx->clip.x1);
}
if (sign(x-x1) == sign(x-x2)) static enum path_transition
return 0; path_transition_right_edge(struct clip_context *ctx, GLfloat x, GLfloat y)
{
return ((ctx->prev.x < ctx->clip.x2) << 1) | (x < ctx->clip.x2);
}
y = m * x + c; static enum path_transition
path_transition_top_edge(struct clip_context *ctx, GLfloat x, GLfloat y)
{
return ((ctx->prev.y >= ctx->clip.y1) << 1) | (y >= ctx->clip.y1);
}
if (sign(y-cy1) == sign(y-cy2)) static enum path_transition
return 0; path_transition_bottom_edge(struct clip_context *ctx, GLfloat x, GLfloat y)
{
return ((ctx->prev.y < ctx->clip.y2) << 1) | (y < ctx->clip.y2);
}
*p = y; static void
return 1; clip_polygon_leftright(struct clip_context *ctx,
enum path_transition transition,
GLfloat x, GLfloat y, GLfloat clip_x)
{
GLfloat yi;
switch (transition) {
case PATH_TRANSITION_IN_TO_IN:
clip_append_vertex(ctx, x, y);
break;
case PATH_TRANSITION_IN_TO_OUT:
yi = clip_intersect_y(ctx->prev.x, ctx->prev.y, x, y, clip_x);
clip_append_vertex(ctx, clip_x, yi);
break;
case PATH_TRANSITION_OUT_TO_IN:
yi = clip_intersect_y(ctx->prev.x, ctx->prev.y, x, y, clip_x);
clip_append_vertex(ctx, clip_x, yi);
clip_append_vertex(ctx, x, y);
break;
case PATH_TRANSITION_OUT_TO_OUT:
/* nothing */
break;
default:
assert(0 && "bad enum path_transition");
} }
/* y = mx + c */ ctx->prev.x = x;
m = (y2 - y1) / (x2 - x1); ctx->prev.y = y;
c = y1 - m * x1; }
/* check for up to two intersections with the four edges static void
* of the clip rect. Note that we don't know the orientation clip_polygon_topbottom(struct clip_context *ctx,
* of the transformed surface wrt. the clip rect. So if when enum path_transition transition,
* there are two intersection points, we need to put the one GLfloat x, GLfloat y, GLfloat clip_y)
* closest to x1,y1 first: {
*/ GLfloat xi;
/* check top clip rect edge: */ switch (transition) {
if (intersect_horiz(cy1, &p)) { case PATH_TRANSITION_IN_TO_IN:
ty[tn] = cy1; clip_append_vertex(ctx, x, y);
tx[tn] = p; break;
tn++; case PATH_TRANSITION_IN_TO_OUT:
xi = clip_intersect_x(ctx->prev.x, ctx->prev.y, x, y, clip_y);
clip_append_vertex(ctx, xi, clip_y);
break;
case PATH_TRANSITION_OUT_TO_IN:
xi = clip_intersect_x(ctx->prev.x, ctx->prev.y, x, y, clip_y);
clip_append_vertex(ctx, xi, clip_y);
clip_append_vertex(ctx, x, y);
break;
case PATH_TRANSITION_OUT_TO_OUT:
/* nothing */
break;
default:
assert(0 && "bad enum path_transition");
} }
/* check right clip rect edge: */ ctx->prev.x = x;
if (intersect_vert(cx2, &p)) { ctx->prev.y = y;
ty[tn] = p; }
tx[tn] = cx2;
tn++;
if (tn == 2)
goto edge_check_done;
}
/* check bottom clip rect edge: */ static void
if (intersect_horiz(cy2, &p)) { clip_context_prepare(struct clip_context *ctx, const struct polygon8 *src,
ty[tn] = cy2; GLfloat *dst_x, GLfloat *dst_y)
tx[tn] = p; {
tn++; ctx->prev.x = src->x[src->n - 1];
if (tn == 2) ctx->prev.y = src->y[src->n - 1];
goto edge_check_done; ctx->vertices.x = dst_x;
} ctx->vertices.y = dst_y;
}
/* check left clip rect edge: */ static int
if (intersect_vert(cx1, &p)) { clip_polygon_left(struct clip_context *ctx, const struct polygon8 *src,
ty[tn] = p; GLfloat *dst_x, GLfloat *dst_y)
tx[tn] = cx1; {
tn++; enum path_transition trans;
} int i;
edge_check_done: clip_context_prepare(ctx, src, dst_x, dst_y);
if (tn == 1) { for (i = 0; i < src->n; i++) {
append_vertex(tx[0], ty[0]); trans = path_transition_left_edge(ctx, src->x[i], src->y[i]);
} else if (tn == 2) { clip_polygon_leftright(ctx, trans, src->x[i], src->y[i],
if (dist_squared(x1, y1, tx[0], ty[0]) < ctx->clip.x1);
dist_squared(x1, y1, tx[1], ty[1])) {
append_vertex(tx[0], ty[0]);
append_vertex(tx[1], ty[1]);
} else {
append_vertex(tx[1], ty[1]);
append_vertex(tx[0], ty[0]);
}
} }
return ctx->vertices.x - dst_x;
}
if (n == last_n) { static int
GLfloat best_x=0, best_y=0; clip_polygon_right(struct clip_context *ctx, const struct polygon8 *src,
uint32_t d, best_d = (unsigned int)-1; /* distance squared */ GLfloat *dst_x, GLfloat *dst_y)
uint32_t max_d = dist_squared(x2, y2, {
x[(i+2) % 4], y[(i+2) % 4]); enum path_transition trans;
int i;
/* if there are no vertices on this line, it could be that
* there is a vertex of the clip rect that is enclosed by
* the transformed surface. Find the vertex of the clip
* rect that is reached by the shortest line perpendicular
* to the current edge, if any.
*
* slope of perpendicular is 1/m, so
*
* cy = -cx/m + c2
* c2 = cy + cx/m
*
*/
int perp_intersect(GLfloat cx, GLfloat cy, uint32_t *d) clip_context_prepare(ctx, src, dst_x, dst_y);
{ for (i = 0; i < src->n; i++) {
GLfloat c2 = cy + cx/m; trans = path_transition_right_edge(ctx, src->x[i], src->y[i]);
GLfloat x = (c2 - c) / (m + 1/m); clip_polygon_leftright(ctx, trans, src->x[i], src->y[i],
ctx->clip.x2);
}
return ctx->vertices.x - dst_x;
}
/* if the x position of the intersection of the static int
* perpendicular with the transformed edge does clip_polygon_top(struct clip_context *ctx, const struct polygon8 *src,
* not lie within the bounds of the edge, then GLfloat *dst_x, GLfloat *dst_y)
* no intersection: {
*/ enum path_transition trans;
if (sign(x-x1) == sign(x-x2)) int i;
return 0;
*d = dist_squared(cx, cy, x, (m * x) + c); clip_context_prepare(ctx, src, dst_x, dst_y);
for (i = 0; i < src->n; i++) {
trans = path_transition_top_edge(ctx, src->x[i], src->y[i]);
clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i],
ctx->clip.y1);
}
return ctx->vertices.x - dst_x;
}
/* if intersection distance is further away than static int
* opposite edge of surface region, it is invalid: clip_polygon_bottom(struct clip_context *ctx, const struct polygon8 *src,
*/ GLfloat *dst_x, GLfloat *dst_y)
if (*d > max_d) {
return 0; enum path_transition trans;
int i;
return 1; clip_context_prepare(ctx, src, dst_x, dst_y);
for (i = 0; i < src->n; i++) {
trans = path_transition_bottom_edge(ctx, src->x[i], src->y[i]);
clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i],
ctx->clip.y2);
} }
return ctx->vertices.x - dst_x;
}
if (perp_intersect(cx1, cy1, &d)) { #define max(a, b) (((a) > (b)) ? (a) : (b))
best_x = cx1; #define min(a, b) (((a) > (b)) ? (b) : (a))
best_y = cy1; #define clip(x, a, b) min(max(x, a), b)
best_d = d;
}
if (perp_intersect(cx1, cy2, &d) && (d < best_d)) { /*
best_x = cx1; * Compute the boundary vertices of the intersection of the global coordinate
best_y = cy2; * aligned rectangle 'rect', and an arbitrary quadrilateral produced from
best_d = d; * 'surf_rect' when transformed from surface coordinates into global coordinates.
} * The vertices are written to 'ex' and 'ey', and the return value is the
* number of vertices. Vertices are produced in clockwise winding order.
* Guarantees to produce either zero vertices, or 3-8 vertices with non-zero
* polygon area.
*/
static int
calculate_edges(struct weston_surface *es, pixman_box32_t *rect,
pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey)
{
struct polygon8 polygon;
struct clip_context ctx;
int i, n;
GLfloat min_x, max_x, min_y, max_y;
struct polygon8 surf = {
{ surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1 },
{ surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2 },
4
};
if (perp_intersect(cx2, cy2, &d) && (d < best_d)) { ctx.clip.x1 = rect->x1;
best_x = cx2; ctx.clip.y1 = rect->y1;
best_y = cy2; ctx.clip.x2 = rect->x2;
best_d = d; ctx.clip.y2 = rect->y2;
}
/* transform surface to screen space: */
for (i = 0; i < surf.n; i++)
weston_surface_to_global_float(es, surf.x[i], surf.y[i],
&surf.x[i], &surf.y[i]);
if (perp_intersect(cx2, cy1, &d) && (d < best_d)) { /* find bounding box: */
best_x = cx2; min_x = max_x = surf.x[0];
best_y = cy1; min_y = max_y = surf.y[0];
best_d = d;
for (i = 1; i < surf.n; i++) {
min_x = min(min_x, surf.x[i]);
max_x = max(max_x, surf.x[i]);
min_y = min(min_y, surf.y[i]);
max_y = max(max_y, surf.y[i]);
} }
if (best_d != (unsigned int)-1) // XXX can this happen? /* First, simple bounding box check to discard early transformed
append_vertex(best_x, best_y); * surface rects that do not intersect with the clip region:
*/
if ((min_x >= ctx.clip.x2) || (max_x <= ctx.clip.x1) ||
(min_y >= ctx.clip.y2) || (max_y <= ctx.clip.y1))
return 0;
/* Simple case, bounding box edges are parallel to surface edges,
* there will be only four edges. We just need to clip the surface
* vertices to the clip rect bounds:
*/
if (!es->transform.enabled) {
for (i = 0; i < surf.n; i++) {
ex[i] = clip(surf.x[i], ctx.clip.x1, ctx.clip.x2);
ey[i] = clip(surf.y[i], ctx.clip.y1, ctx.clip.y2);
} }
return surf.n;
} }
/* Transformed case: use a general polygon clipping algorithm to
* clip the surface rectangle with each side of 'rect'.
* The algorithm is Sutherland-Hodgman, as explained in
* http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c8965/Polygon-Clipping.htm
* but without looking at any of that code.
*/
polygon.n = clip_polygon_left(&ctx, &surf, polygon.x, polygon.y);
surf.n = clip_polygon_right(&ctx, &polygon, surf.x, surf.y);
polygon.n = clip_polygon_top(&ctx, &surf, polygon.x, polygon.y);
surf.n = clip_polygon_bottom(&ctx, &polygon, surf.x, surf.y);
/* Get rid of duplicate vertices */
ex[0] = surf.x[0];
ey[0] = surf.y[0];
n = 1;
for (i = 1; i < surf.n; i++) {
if (float_difference(ex[n - 1], surf.x[i]) == 0.0f &&
float_difference(ey[n - 1], surf.y[i]) == 0.0f)
continue;
ex[n] = surf.x[i];
ey[n] = surf.y[i];
n++;
} }
if (float_difference(ex[n - 1], surf.x[0]) == 0.0f &&
float_difference(ey[n - 1], surf.y[0]) == 0.0f)
n--;
if (n < 3)
return 0;
return n; return n;
} }
/* ---------------------- copied ends -----------------------*/ /* ---------------------- copied ends -----------------------*/
static void static void

@ -25,6 +25,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <float.h>
#include <assert.h>
#include "compositor.h" #include "compositor.h"
@ -64,290 +66,363 @@ print_egl_error_state(void)
egl_error_string(code), (long)code); egl_error_string(code), (long)code);
} }
#define max(a, b) (((a) > (b)) ? (a) : (b)) struct polygon8 {
#define min(a, b) (((a) > (b)) ? (b) : (a)) GLfloat x[8];
#define clip(x, a, b) min(max(x, a), b) GLfloat y[8];
#define sign(x) ((x) >= 0) int n;
};
static int struct clip_context {
calculate_edges(struct weston_surface *es, pixman_box32_t *rect, struct {
pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey) GLfloat x;
GLfloat y;
} prev;
struct {
GLfloat x1, y1;
GLfloat x2, y2;
} clip;
struct {
GLfloat *x;
GLfloat *y;
} vertices;
};
static GLfloat
float_difference(GLfloat a, GLfloat b)
{ {
int i, n = 0; /* http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ */
GLfloat min_x, max_x, min_y, max_y; static const GLfloat max_diff = 4.0f * FLT_MIN;
GLfloat x[4] = { static const GLfloat max_rel_diff = 4.0e-5;
surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1, GLfloat diff = a - b;
}; GLfloat adiff = fabsf(diff);
GLfloat y[4] = {
surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2,
};
GLfloat cx1 = rect->x1;
GLfloat cx2 = rect->x2;
GLfloat cy1 = rect->y1;
GLfloat cy2 = rect->y2;
GLfloat dist_squared(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
{
GLfloat dx = (x1 - x2);
GLfloat dy = (y1 - y2);
return dx * dx + dy * dy;
}
void append_vertex(GLfloat x, GLfloat y) if (adiff <= max_diff)
{ return 0.0f;
/* don't emit duplicate vertices: */
if ((n > 0) && (ex[n-1] == x) && (ey[n-1] == y))
return;
ex[n] = x;
ey[n] = y;
n++;
}
/* transform surface to screen space: */ a = fabsf(a);
for (i = 0; i < 4; i++) b = fabsf(b);
weston_surface_to_global_float(es, x[i], y[i], &x[i], &y[i]); if (adiff <= (a > b ? a : b) * max_rel_diff)
return 0.0f;
/* find bounding box: */ return diff;
min_x = max_x = x[0]; }
min_y = max_y = y[0];
for (i = 1; i < 4; i++) {
min_x = min(min_x, x[i]);
max_x = max(max_x, x[i]);
min_y = min(min_y, y[i]);
max_y = max(max_y, y[i]);
}
/* First, simple bounding box check to discard early transformed /* A line segment (p1x, p1y)-(p2x, p2y) intersects the line x = x_arg.
* surface rects that do not intersect with the clip region: * Compute the y coordinate of the intersection.
*/ */
if ((min_x > cx2) || (max_x < cx1) || static GLfloat
(min_y > cy2) || (max_y < cy1)) clip_intersect_y(GLfloat p1x, GLfloat p1y, GLfloat p2x, GLfloat p2y,
return 0; GLfloat x_arg)
{
GLfloat a;
GLfloat diff = float_difference(p1x, p2x);
/* Simple case, bounding box edges are parallel to surface edges, /* Practically vertical line segment, yet the end points have already
* there will be only four edges. We just need to clip the surface * been determined to be on different sides of the line. Therefore
* vertices to the clip rect bounds: * the line segment is part of the line and intersects everywhere.
* Return the end point, so we use the whole line segment.
*/ */
if (!es->transform.enabled) { if (diff == 0.0f)
for (i = 0; i < 4; i++) { return p2y;
ex[n] = clip(x[i], cx1, cx2);
ey[n] = clip(y[i], cy1, cy2);
n++;
}
return 4;
}
/* Hard case, transformation applied. We need to find the vertices a = (x_arg - p2x) / diff;
* of the shape that is the intersection of the clip rect and return p2y + (p1y - p2y) * a;
* transformed surface. This can be anything from 3 to 8 sides. }
*
* Observation: all the resulting vertices will be the intersection
* points of the transformed surface and the clip rect, plus the
* vertices of the clip rect which are enclosed by the transformed
* surface and the vertices of the transformed surface which are
* enclosed by the clip rect.
*
* Observation: there will be zero, one, or two resulting vertices
* for each edge of the src rect.
*
* Loop over four edges of the transformed rect:
*/
for (i = 0; i < 4; i++) {
GLfloat x1, y1, x2, y2;
int last_n = n;
x1 = x[i];
y1 = y[i];
/* if this vertex is contained in the clip rect, use it as-is: */
if ((cx1 <= x1) && (x1 <= cx2) &&
(cy1 <= y1) && (y1 <= cy2))
append_vertex(x1, y1);
/* for remaining, we consider the point as part of a line: */
x2 = x[(i+1) % 4];
y2 = y[(i+1) % 4];
if (x1 == x2) {
append_vertex(clip(x1, cx1, cx2), clip(y1, cy1, cy2));
append_vertex(clip(x2, cx1, cx2), clip(y2, cy1, cy2));
} else if (y1 == y2) {
append_vertex(clip(x1, cx1, cx2), clip(y1, cy1, cy2));
append_vertex(clip(x2, cx1, cx2), clip(y2, cy1, cy2));
} else {
GLfloat m, c, p;
GLfloat tx[2], ty[2];
int tn = 0;
int intersect_horiz(GLfloat y, GLfloat *p) /* A line segment (p1x, p1y)-(p2x, p2y) intersects the line y = y_arg.
{ * Compute the x coordinate of the intersection.
GLfloat x; */
static GLfloat
clip_intersect_x(GLfloat p1x, GLfloat p1y, GLfloat p2x, GLfloat p2y,
GLfloat y_arg)
{
GLfloat a;
GLfloat diff = float_difference(p1y, p2y);
/* if y does not lie between y1 and y2, no /* Practically horizontal line segment, yet the end points have already
* intersection possible * been determined to be on different sides of the line. Therefore
* the line segment is part of the line and intersects everywhere.
* Return the end point, so we use the whole line segment.
*/ */
if (sign(y-y1) == sign(y-y2)) if (diff == 0.0f)
return 0; return p2x;
x = (y - c) / m; a = (y_arg - p2y) / diff;
return p2x + (p1x - p2x) * a;
}
/* if x does not lie between cx1 and cx2, no enum path_transition {
* intersection: PATH_TRANSITION_OUT_TO_OUT = 0,
*/ PATH_TRANSITION_OUT_TO_IN = 1,
if (sign(x-cx1) == sign(x-cx2)) PATH_TRANSITION_IN_TO_OUT = 2,
return 0; PATH_TRANSITION_IN_TO_IN = 3,
};
*p = x; static void
return 1; clip_append_vertex(struct clip_context *ctx, GLfloat x, GLfloat y)
} {
*ctx->vertices.x++ = x;
*ctx->vertices.y++ = y;
}
int intersect_vert(GLfloat x, GLfloat *p) static enum path_transition
{ path_transition_left_edge(struct clip_context *ctx, GLfloat x, GLfloat y)
GLfloat y; {
return ((ctx->prev.x >= ctx->clip.x1) << 1) | (x >= ctx->clip.x1);
}
if (sign(x-x1) == sign(x-x2)) static enum path_transition
return 0; path_transition_right_edge(struct clip_context *ctx, GLfloat x, GLfloat y)
{
return ((ctx->prev.x < ctx->clip.x2) << 1) | (x < ctx->clip.x2);
}
y = m * x + c; static enum path_transition
path_transition_top_edge(struct clip_context *ctx, GLfloat x, GLfloat y)
{
return ((ctx->prev.y >= ctx->clip.y1) << 1) | (y >= ctx->clip.y1);
}
if (sign(y-cy1) == sign(y-cy2)) static enum path_transition
return 0; path_transition_bottom_edge(struct clip_context *ctx, GLfloat x, GLfloat y)
{
return ((ctx->prev.y < ctx->clip.y2) << 1) | (y < ctx->clip.y2);
}
*p = y; static void
return 1; clip_polygon_leftright(struct clip_context *ctx,
enum path_transition transition,
GLfloat x, GLfloat y, GLfloat clip_x)
{
GLfloat yi;
switch (transition) {
case PATH_TRANSITION_IN_TO_IN:
clip_append_vertex(ctx, x, y);
break;
case PATH_TRANSITION_IN_TO_OUT:
yi = clip_intersect_y(ctx->prev.x, ctx->prev.y, x, y, clip_x);
clip_append_vertex(ctx, clip_x, yi);
break;
case PATH_TRANSITION_OUT_TO_IN:
yi = clip_intersect_y(ctx->prev.x, ctx->prev.y, x, y, clip_x);
clip_append_vertex(ctx, clip_x, yi);
clip_append_vertex(ctx, x, y);
break;
case PATH_TRANSITION_OUT_TO_OUT:
/* nothing */
break;
default:
assert(0 && "bad enum path_transition");
} }
/* y = mx + c */ ctx->prev.x = x;
m = (y2 - y1) / (x2 - x1); ctx->prev.y = y;
c = y1 - m * x1; }
/* check for up to two intersections with the four edges static void
* of the clip rect. Note that we don't know the orientation clip_polygon_topbottom(struct clip_context *ctx,
* of the transformed surface wrt. the clip rect. So if when enum path_transition transition,
* there are two intersection points, we need to put the one GLfloat x, GLfloat y, GLfloat clip_y)
* closest to x1,y1 first: {
*/ GLfloat xi;
/* check top clip rect edge: */ switch (transition) {
if (intersect_horiz(cy1, &p)) { case PATH_TRANSITION_IN_TO_IN:
ty[tn] = cy1; clip_append_vertex(ctx, x, y);
tx[tn] = p; break;
tn++; case PATH_TRANSITION_IN_TO_OUT:
xi = clip_intersect_x(ctx->prev.x, ctx->prev.y, x, y, clip_y);
clip_append_vertex(ctx, xi, clip_y);
break;
case PATH_TRANSITION_OUT_TO_IN:
xi = clip_intersect_x(ctx->prev.x, ctx->prev.y, x, y, clip_y);
clip_append_vertex(ctx, xi, clip_y);
clip_append_vertex(ctx, x, y);
break;
case PATH_TRANSITION_OUT_TO_OUT:
/* nothing */
break;
default:
assert(0 && "bad enum path_transition");
} }
/* check right clip rect edge: */ ctx->prev.x = x;
if (intersect_vert(cx2, &p)) { ctx->prev.y = y;
ty[tn] = p; }
tx[tn] = cx2;
tn++;
if (tn == 2)
goto edge_check_done;
}
/* check bottom clip rect edge: */ static void
if (intersect_horiz(cy2, &p)) { clip_context_prepare(struct clip_context *ctx, const struct polygon8 *src,
ty[tn] = cy2; GLfloat *dst_x, GLfloat *dst_y)
tx[tn] = p; {
tn++; ctx->prev.x = src->x[src->n - 1];
if (tn == 2) ctx->prev.y = src->y[src->n - 1];
goto edge_check_done; ctx->vertices.x = dst_x;
} ctx->vertices.y = dst_y;
}
/* check left clip rect edge: */ static int
if (intersect_vert(cx1, &p)) { clip_polygon_left(struct clip_context *ctx, const struct polygon8 *src,
ty[tn] = p; GLfloat *dst_x, GLfloat *dst_y)
tx[tn] = cx1; {
tn++; enum path_transition trans;
} int i;
edge_check_done: clip_context_prepare(ctx, src, dst_x, dst_y);
if (tn == 1) { for (i = 0; i < src->n; i++) {
append_vertex(tx[0], ty[0]); trans = path_transition_left_edge(ctx, src->x[i], src->y[i]);
} else if (tn == 2) { clip_polygon_leftright(ctx, trans, src->x[i], src->y[i],
if (dist_squared(x1, y1, tx[0], ty[0]) < ctx->clip.x1);
dist_squared(x1, y1, tx[1], ty[1])) {
append_vertex(tx[0], ty[0]);
append_vertex(tx[1], ty[1]);
} else {
append_vertex(tx[1], ty[1]);
append_vertex(tx[0], ty[0]);
}
} }
return ctx->vertices.x - dst_x;
}
if (n == last_n) { static int
GLfloat best_x=0, best_y=0; clip_polygon_right(struct clip_context *ctx, const struct polygon8 *src,
uint32_t d, best_d = (unsigned int)-1; /* distance squared */ GLfloat *dst_x, GLfloat *dst_y)
uint32_t max_d = dist_squared(x2, y2, {
x[(i+2) % 4], y[(i+2) % 4]); enum path_transition trans;
int i;
/* if there are no vertices on this line, it could be that
* there is a vertex of the clip rect that is enclosed by
* the transformed surface. Find the vertex of the clip
* rect that is reached by the shortest line perpendicular
* to the current edge, if any.
*
* slope of perpendicular is 1/m, so
*
* cy = -cx/m + c2
* c2 = cy + cx/m
*
*/
int perp_intersect(GLfloat cx, GLfloat cy, uint32_t *d) clip_context_prepare(ctx, src, dst_x, dst_y);
{ for (i = 0; i < src->n; i++) {
GLfloat c2 = cy + cx/m; trans = path_transition_right_edge(ctx, src->x[i], src->y[i]);
GLfloat x = (c2 - c) / (m + 1/m); clip_polygon_leftright(ctx, trans, src->x[i], src->y[i],
ctx->clip.x2);
}
return ctx->vertices.x - dst_x;
}
/* if the x position of the intersection of the static int
* perpendicular with the transformed edge does clip_polygon_top(struct clip_context *ctx, const struct polygon8 *src,
* not lie within the bounds of the edge, then GLfloat *dst_x, GLfloat *dst_y)
* no intersection: {
*/ enum path_transition trans;
if (sign(x-x1) == sign(x-x2)) int i;
return 0;
*d = dist_squared(cx, cy, x, (m * x) + c); clip_context_prepare(ctx, src, dst_x, dst_y);
for (i = 0; i < src->n; i++) {
trans = path_transition_top_edge(ctx, src->x[i], src->y[i]);
clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i],
ctx->clip.y1);
}
return ctx->vertices.x - dst_x;
}
/* if intersection distance is further away than static int
* opposite edge of surface region, it is invalid: clip_polygon_bottom(struct clip_context *ctx, const struct polygon8 *src,
*/ GLfloat *dst_x, GLfloat *dst_y)
if (*d > max_d) {
return 0; enum path_transition trans;
int i;
return 1; clip_context_prepare(ctx, src, dst_x, dst_y);
for (i = 0; i < src->n; i++) {
trans = path_transition_bottom_edge(ctx, src->x[i], src->y[i]);
clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i],
ctx->clip.y2);
} }
return ctx->vertices.x - dst_x;
}
if (perp_intersect(cx1, cy1, &d)) { #define max(a, b) (((a) > (b)) ? (a) : (b))
best_x = cx1; #define min(a, b) (((a) > (b)) ? (b) : (a))
best_y = cy1; #define clip(x, a, b) min(max(x, a), b)
best_d = d;
}
if (perp_intersect(cx1, cy2, &d) && (d < best_d)) { /*
best_x = cx1; * Compute the boundary vertices of the intersection of the global coordinate
best_y = cy2; * aligned rectangle 'rect', and an arbitrary quadrilateral produced from
best_d = d; * 'surf_rect' when transformed from surface coordinates into global coordinates.
} * The vertices are written to 'ex' and 'ey', and the return value is the
* number of vertices. Vertices are produced in clockwise winding order.
* Guarantees to produce either zero vertices, or 3-8 vertices with non-zero
* polygon area.
*/
static int
calculate_edges(struct weston_surface *es, pixman_box32_t *rect,
pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey)
{
struct polygon8 polygon;
struct clip_context ctx;
int i, n;
GLfloat min_x, max_x, min_y, max_y;
struct polygon8 surf = {
{ surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1 },
{ surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2 },
4
};
if (perp_intersect(cx2, cy2, &d) && (d < best_d)) { ctx.clip.x1 = rect->x1;
best_x = cx2; ctx.clip.y1 = rect->y1;
best_y = cy2; ctx.clip.x2 = rect->x2;
best_d = d; ctx.clip.y2 = rect->y2;
}
if (perp_intersect(cx2, cy1, &d) && (d < best_d)) { /* transform surface to screen space: */
best_x = cx2; for (i = 0; i < surf.n; i++)
best_y = cy1; weston_surface_to_global_float(es, surf.x[i], surf.y[i],
best_d = d; &surf.x[i], &surf.y[i]);
/* find bounding box: */
min_x = max_x = surf.x[0];
min_y = max_y = surf.y[0];
for (i = 1; i < surf.n; i++) {
min_x = min(min_x, surf.x[i]);
max_x = max(max_x, surf.x[i]);
min_y = min(min_y, surf.y[i]);
max_y = max(max_y, surf.y[i]);
} }
if (best_d != (unsigned int)-1) // XXX can this happen? /* First, simple bounding box check to discard early transformed
append_vertex(best_x, best_y); * surface rects that do not intersect with the clip region:
*/
if ((min_x >= ctx.clip.x2) || (max_x <= ctx.clip.x1) ||
(min_y >= ctx.clip.y2) || (max_y <= ctx.clip.y1))
return 0;
/* Simple case, bounding box edges are parallel to surface edges,
* there will be only four edges. We just need to clip the surface
* vertices to the clip rect bounds:
*/
if (!es->transform.enabled) {
for (i = 0; i < surf.n; i++) {
ex[i] = clip(surf.x[i], ctx.clip.x1, ctx.clip.x2);
ey[i] = clip(surf.y[i], ctx.clip.y1, ctx.clip.y2);
} }
return surf.n;
} }
/* Transformed case: use a general polygon clipping algorithm to
* clip the surface rectangle with each side of 'rect'.
* The algorithm is Sutherland-Hodgman, as explained in
* http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c8965/Polygon-Clipping.htm
* but without looking at any of that code.
*/
polygon.n = clip_polygon_left(&ctx, &surf, polygon.x, polygon.y);
surf.n = clip_polygon_right(&ctx, &polygon, surf.x, surf.y);
polygon.n = clip_polygon_top(&ctx, &surf, polygon.x, polygon.y);
surf.n = clip_polygon_bottom(&ctx, &polygon, surf.x, surf.y);
/* Get rid of duplicate vertices */
ex[0] = surf.x[0];
ey[0] = surf.y[0];
n = 1;
for (i = 1; i < surf.n; i++) {
if (float_difference(ex[n - 1], surf.x[i]) == 0.0f &&
float_difference(ey[n - 1], surf.y[i]) == 0.0f)
continue;
ex[n] = surf.x[i];
ey[n] = surf.y[i];
n++;
} }
if (float_difference(ex[n - 1], surf.x[0]) == 0.0f &&
float_difference(ey[n - 1], surf.y[0]) == 0.0f)
n--;
if (n < 3)
return 0;
return n; return n;
} }

Loading…
Cancel
Save