diff --git a/.gitlab-ci/run-tests.sh b/.gitlab-ci/run-tests.sh index 51e2db68fd..4a206ef2b0 100755 --- a/.gitlab-ci/run-tests.sh +++ b/.gitlab-ci/run-tests.sh @@ -50,6 +50,7 @@ case "${backend}" in --suite=gtk \ --no-suite=failing \ --no-suite=flaky \ + --no-suite=${backend}_failing \ --no-suite=gsk-compare-broadway exit_code=$? diff --git a/gsk/gl/gskglrenderer.c b/gsk/gl/gskglrenderer.c index 306b042c98..007dae1f87 100644 --- a/gsk/gl/gskglrenderer.c +++ b/gsk/gl/gskglrenderer.c @@ -356,7 +356,8 @@ gsk_gl_renderer_render_texture (GskRenderer *renderer, for (x = 0; x < width; x += max_size) { texture = gsk_gl_renderer_render_texture (renderer, root, - &GRAPHENE_RECT_INIT (x, y, + &GRAPHENE_RECT_INIT (viewport->origin.x + x, + viewport->origin.y + y, MIN (max_size, viewport->size.width - x), MIN (max_size, viewport->size.height - y))); gdk_texture_download (texture, diff --git a/gsk/gl/gskglrenderjob.c b/gsk/gl/gskglrenderjob.c index d3bdef751d..7fa6408fc2 100644 --- a/gsk/gl/gskglrenderjob.c +++ b/gsk/gl/gskglrenderjob.c @@ -54,27 +54,6 @@ /* Make sure gradient stops fits in packed array_count */ G_STATIC_ASSERT ((MAX_GRADIENT_STOPS * 5) < (1 << GSK_GL_UNIFORM_ARRAY_BITS)); -#define rounded_rect_top_left(r) \ - (GRAPHENE_RECT_INIT(r->bounds.origin.x, \ - r->bounds.origin.y, \ - r->corner[0].width, r->corner[0].height)) -#define rounded_rect_top_right(r) \ - (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[1].width, \ - r->bounds.origin.y, \ - r->corner[1].width, r->corner[1].height)) -#define rounded_rect_bottom_right(r) \ - (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[2].width, \ - r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \ - r->corner[2].width, r->corner[2].height)) -#define rounded_rect_bottom_left(r) \ - (GRAPHENE_RECT_INIT(r->bounds.origin.x, \ - r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \ - r->corner[3].width, r->corner[3].height)) -#define rounded_rect_corner0(r) rounded_rect_top_left(r) -#define rounded_rect_corner1(r) rounded_rect_top_right(r) -#define rounded_rect_corner2(r) rounded_rect_bottom_right(r) -#define rounded_rect_corner3(r) rounded_rect_bottom_left(r) -#define rounded_rect_corner(r, i) (rounded_rect_corner##i(r)) #define ALPHA_IS_CLEAR(alpha) ((alpha) < ((float) 0x00ff / (float) 0xffff)) #define RGBA_IS_CLEAR(rgba) ALPHA_IS_CLEAR((rgba)->alpha) @@ -429,70 +408,6 @@ rect_intersects (const graphene_rect_t *r1, return TRUE; } -static inline gboolean -rounded_rect_has_corner (const GskRoundedRect *r, - guint i) -{ - return r->corner[i].width > 0 && r->corner[i].height > 0; -} - -/* Current clip is NOT rounded but new one is definitely! */ -static inline gboolean -intersect_rounded_rectilinear (const graphene_rect_t *non_rounded, - const GskRoundedRect *rounded, - GskRoundedRect *result) -{ - gboolean corners[4]; - - /* Intersects with top left corner? */ - corners[0] = rounded_rect_has_corner (rounded, 0) && - rect_intersects (non_rounded, - &rounded_rect_corner (rounded, 0)); - if (corners[0] && !rect_contains_rect (non_rounded, - &rounded_rect_corner (rounded, 0))) - return FALSE; - - /* top right ? */ - corners[1] = rounded_rect_has_corner (rounded, 1) && - rect_intersects (non_rounded, - &rounded_rect_corner (rounded, 1)); - if (corners[1] && !rect_contains_rect (non_rounded, - &rounded_rect_corner (rounded, 1))) - return FALSE; - - /* bottom right ? */ - corners[2] = rounded_rect_has_corner (rounded, 2) && - rect_intersects (non_rounded, - &rounded_rect_corner (rounded, 2)); - if (corners[2] && !rect_contains_rect (non_rounded, - &rounded_rect_corner (rounded, 2))) - return FALSE; - - /* bottom left ? */ - corners[3] = rounded_rect_has_corner (rounded, 3) && - rect_intersects (non_rounded, - &rounded_rect_corner (rounded, 3)); - if (corners[3] && !rect_contains_rect (non_rounded, - &rounded_rect_corner (rounded, 3))) - return FALSE; - - /* We do intersect with at least one of the corners, but in such a way that the - * intersection between the two clips can still be represented by a single rounded - * rect in a trivial way. do that. - */ - graphene_rect_intersection (non_rounded, &rounded->bounds, &result->bounds); - - for (guint i = 0; i < 4; i++) - { - if (corners[i]) - result->corner[i] = rounded->corner[i]; - else - result->corner[i].width = result->corner[i].height = 0; - } - - return TRUE; -} - static inline void init_projection_matrix (graphene_matrix_t *projection, const graphene_rect_t *viewport) @@ -551,13 +466,14 @@ extract_matrix_metadata (GskGLRenderModelview *modelview) case GSK_TRANSFORM_CATEGORY_2D: { - float xx, xy, yx, yy, dx, dy; + float skew_x, skew_y, angle, dx, dy; - gsk_transform_to_2d (modelview->transform, - &xx, &xy, &yx, &yy, &dx, &dy); - - modelview->scale_x = sqrtf (xx * xx + xy * xy); - modelview->scale_y = sqrtf (yx * yx + yy * yy); + gsk_transform_to_2d_components (modelview->transform, + &skew_x, &skew_y, + &modelview->scale_x, &modelview->scale_y, + &angle, &dx, &dy); + modelview->dx = 0; + modelview->dy = 0; } break; @@ -918,10 +834,10 @@ gsk_gl_render_job_untransform_bounds (GskGLRenderJob *job, } static inline void -gsk_gl_render_job_transform_rounded_rect (GskGLRenderJob *job, +gsk_gl_render_job_translate_rounded_rect (GskGLRenderJob *job, const GskRoundedRect *rect, GskRoundedRect *out_rect) -{ + { out_rect->bounds.origin.x = job->offset_x + rect->bounds.origin.x; out_rect->bounds.origin.y = job->offset_y + rect->bounds.origin.y; out_rect->bounds.size.width = rect->bounds.size.width; @@ -929,6 +845,52 @@ gsk_gl_render_job_transform_rounded_rect (GskGLRenderJob *job, memcpy (out_rect->corner, rect->corner, sizeof rect->corner); } +static inline void +rounded_rect_scale_corners (const GskRoundedRect *rect, + GskRoundedRect *out_rect, + float scale_x, + float scale_y) +{ + for (guint i = 0; i < G_N_ELEMENTS (out_rect->corner); i++) + { + out_rect->corner[i].width = rect->corner[i].width * fabs (scale_x); + out_rect->corner[i].height = rect->corner[i].height * fabs (scale_y); + } + + if (scale_x < 0) + { + graphene_size_t p; + + p = out_rect->corner[GSK_CORNER_TOP_LEFT]; + out_rect->corner[GSK_CORNER_TOP_LEFT] = out_rect->corner[GSK_CORNER_TOP_RIGHT]; + out_rect->corner[GSK_CORNER_TOP_RIGHT] = p; + p = out_rect->corner[GSK_CORNER_BOTTOM_LEFT]; + out_rect->corner[GSK_CORNER_BOTTOM_LEFT] = out_rect->corner[GSK_CORNER_BOTTOM_RIGHT]; + out_rect->corner[GSK_CORNER_BOTTOM_RIGHT] = p; + } + + if (scale_y < 0) + { + graphene_size_t p; + + p = out_rect->corner[GSK_CORNER_TOP_LEFT]; + out_rect->corner[GSK_CORNER_TOP_LEFT] = out_rect->corner[GSK_CORNER_BOTTOM_LEFT]; + out_rect->corner[GSK_CORNER_BOTTOM_LEFT] = p; + p = out_rect->corner[GSK_CORNER_TOP_RIGHT]; + out_rect->corner[GSK_CORNER_TOP_RIGHT] = out_rect->corner[GSK_CORNER_BOTTOM_RIGHT]; + out_rect->corner[GSK_CORNER_BOTTOM_RIGHT] = p; + } +} + +static inline void +gsk_gl_render_job_transform_rounded_rect (GskGLRenderJob *job, + const GskRoundedRect *rect, + GskRoundedRect *out_rect) +{ + gsk_gl_render_job_transform_bounds (job, &rect->bounds, &out_rect->bounds); + rounded_rect_scale_corners (rect, out_rect, job->scale_x, job->scale_y); +} + static inline void rounded_rect_get_inner (const GskRoundedRect *rect, graphene_rect_t *inner) @@ -1231,13 +1193,12 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job, { float scale_x = job->scale_x; float scale_y = job->scale_y; - int surface_width = ceilf (node->bounds.size.width * scale_x); - int surface_height = ceilf (node->bounds.size.height * scale_y); + int surface_width = ceilf (node->bounds.size.width * fabs (scale_x)); + int surface_height = ceilf (node->bounds.size.height * fabs (scale_y)); GdkTexture *texture; cairo_surface_t *surface; cairo_surface_t *rendered_surface; cairo_t *cr; - int cached_id; int texture_id; GskTextureKey key; @@ -1249,18 +1210,10 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job, key.scale_x = scale_x; key.scale_y = scale_y; - cached_id = gsk_gl_driver_lookup_texture (job->driver, &key); + texture_id = gsk_gl_driver_lookup_texture (job->driver, &key); - if (cached_id != 0) - { - gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); - gsk_gl_program_set_uniform_texture (job->current_program, - UNIFORM_SHARED_SOURCE, 0, - GL_TEXTURE_2D, GL_TEXTURE0, cached_id); - gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds); - gsk_gl_render_job_end_draw (job); - return; - } + if (texture_id != 0) + goto done; /* We first draw the recording surface on an image surface, * just because the scaleY(-1) later otherwise screws up the @@ -1270,7 +1223,7 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job, surface_width, surface_height); - cairo_surface_set_device_scale (rendered_surface, scale_x, scale_y); + cairo_surface_set_device_scale (rendered_surface, fabs (scale_x), fabs (scale_y)); cr = cairo_create (rendered_surface); cairo_save (cr); @@ -1284,15 +1237,16 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job, surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, surface_width, surface_height); - cairo_surface_set_device_scale (surface, scale_x, scale_y); + cairo_surface_set_device_scale (surface, fabs (scale_x), fabs (scale_y)); cr = cairo_create (surface); /* We draw upside down here, so it matches what GL does. */ cairo_save (cr); - cairo_scale (cr, 1, -1); - cairo_translate (cr, 0, - surface_height / scale_y); + cairo_scale (cr, scale_x < 0 ? -1 : 1, scale_y < 0 ? 1 : -1); + cairo_translate (cr, scale_x < 0 ? - surface_width / fabs (scale_x) : 0, + scale_y < 0 ? 0 : - surface_height / fabs (scale_y)); cairo_set_source_surface (cr, rendered_surface, 0, 0); - cairo_rectangle (cr, 0, 0, surface_width / scale_x, surface_height / scale_y); + cairo_rectangle (cr, 0, 0, surface_width / fabs (scale_x), surface_height / fabs (scale_y)); cairo_fill (cr); cairo_restore (cr); @@ -1331,6 +1285,16 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job, gsk_gl_driver_cache_texture (job->driver, &key, texture_id); +done: + if (scale_x < 0 || scale_y < 0) + { + GskTransform *transform = gsk_transform_translate (NULL, + &GRAPHENE_POINT_INIT (scale_x < 0 ? - surface_width : 0, + scale_y < 0 ? - surface_height : 0)); + gsk_gl_render_job_push_modelview (job, transform); + gsk_transform_unref (transform); + } + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); gsk_gl_program_set_uniform_texture (job->current_program, UNIFORM_SHARED_SOURCE, 0, @@ -1339,6 +1303,9 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job, texture_id); gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds); gsk_gl_render_job_end_draw (job); + + if (scale_x < 0 || scale_y < 0) + gsk_gl_render_job_pop_modelview (job); } static guint @@ -1493,10 +1460,10 @@ blur_node (GskGLRenderJob *job, offscreen->texture_id = blur_offscreen (job, offscreen, - texture_width * scale_x, - texture_height * scale_y, - blur_radius * scale_x, - blur_radius * scale_y); + texture_width * fabs (scale_x), + texture_height * fabs (scale_y), + blur_radius * fabs (scale_x), + blur_radius * fabs (scale_y)); init_full_texture_region (offscreen); } @@ -1678,6 +1645,7 @@ gsk_gl_render_job_visit_clipped_child (GskGLRenderJob *job, { graphene_rect_t transformed_clip; GskRoundedRect intersection; + GskRoundedRectIntersection result; gsk_gl_render_job_transform_bounds (job, clip, &transformed_clip); @@ -1691,10 +1659,17 @@ gsk_gl_render_job_visit_clipped_child (GskGLRenderJob *job, gsk_gl_render_job_push_clip (job, &intersection); gsk_gl_render_job_visit_node (job, child); gsk_gl_render_job_pop_clip (job); + return; } - else if (intersect_rounded_rectilinear (&transformed_clip, - &job->current_clip->rect, - &intersection)) + + result = gsk_rounded_rect_intersect_with_rect (&job->current_clip->rect, + &transformed_clip, + &intersection); + + if (result == GSK_INTERSECTION_EMPTY) + return; + + if (result == GSK_INTERSECTION_NONEMPTY) { gsk_gl_render_job_push_clip (job, &intersection); gsk_gl_render_job_visit_node (job, child); @@ -1741,28 +1716,26 @@ gsk_gl_render_job_visit_rounded_clip_node (GskGLRenderJob *job, const GskRenderNode *child = gsk_rounded_clip_node_get_child (node); const GskRoundedRect *clip = gsk_rounded_clip_node_get_clip (node); GskRoundedRect transformed_clip; - float scale_x = job->scale_x; - float scale_y = job->scale_y; gboolean need_offscreen; if (node_is_invisible (child)) return; - gsk_gl_render_job_transform_bounds (job, &clip->bounds, &transformed_clip.bounds); - - for (guint i = 0; i < G_N_ELEMENTS (transformed_clip.corner); i++) - { - transformed_clip.corner[i].width = clip->corner[i].width * scale_x; - transformed_clip.corner[i].height = clip->corner[i].height * scale_y; - } + gsk_gl_render_job_transform_rounded_rect (job, clip, &transformed_clip); if (job->current_clip->is_rectilinear) { GskRoundedRect intersected_clip; + GskRoundedRectIntersection result; - if (intersect_rounded_rectilinear (&job->current_clip->rect.bounds, - &transformed_clip, - &intersected_clip)) + result = gsk_rounded_rect_intersect_with_rect (&transformed_clip, + &job->current_clip->rect.bounds, + &intersected_clip); + + if (result == GSK_INTERSECTION_EMPTY) + return; + + if (result == GSK_INTERSECTION_NONEMPTY) { gsk_gl_render_job_push_clip (job, &intersected_clip); gsk_gl_render_job_visit_node (job, child); @@ -1914,7 +1887,7 @@ gsk_gl_render_job_visit_border_node (GskGLRenderJob *job, sizes[3].w = MAX (widths[3], rounded_outline->corner[3].width); } - gsk_gl_render_job_transform_rounded_rect (job, rounded_outline, &outline); + gsk_gl_render_job_translate_rounded_rect (job, rounded_outline, &outline); gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, border)); @@ -2018,7 +1991,7 @@ gsk_gl_render_job_visit_css_background (GskGLRenderJob *job, rgba_to_half (&gsk_border_node_get_colors (node2)[0], color); rgba_to_half (gsk_color_node_get_color (child), color2); - gsk_gl_render_job_transform_rounded_rect (job, rounded_outline, &outline); + gsk_gl_render_job_translate_rounded_rect (job, rounded_outline, &outline); gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, filled_border)); @@ -2159,6 +2132,7 @@ gsk_gl_render_job_visit_transform_node (GskGLRenderJob *job, scale = gsk_transform_translate (gsk_transform_scale (NULL, sx, sy), &GRAPHENE_POINT_INIT (tx, ty)); gsk_gl_render_job_push_modelview (job, scale); transform = gsk_transform_transform (gsk_transform_invert (scale), transform); + gsk_transform_unref (scale); } } @@ -2209,7 +2183,7 @@ gsk_gl_render_job_visit_unblurred_inset_shadow_node (GskGLRenderJob *job, GskRoundedRect transformed_outline; guint16 color[4]; - gsk_gl_render_job_transform_rounded_rect (job, outline, &transformed_outline); + gsk_gl_render_job_translate_rounded_rect (job, outline, &transformed_outline); gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow)); gsk_gl_program_set_uniform_rounded_rect (job->current_program, @@ -2309,7 +2283,7 @@ gsk_gl_render_job_visit_blurred_inset_shadow_node (GskGLRenderJob *job, prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id); gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); - gsk_gl_render_job_transform_rounded_rect (job, &outline_to_blur, &transformed_outline); + gsk_gl_render_job_translate_rounded_rect (job, &outline_to_blur, &transformed_outline); /* Actual inset shadow outline drawing */ gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow)); @@ -2342,8 +2316,8 @@ gsk_gl_render_job_visit_blurred_inset_shadow_node (GskGLRenderJob *job, &offscreen, texture_width, texture_height, - blur_radius * scale_x, - blur_radius * scale_y); + blur_radius * fabs (scale_x), + blur_radius * fabs (scale_y)); gsk_gl_driver_release_render_target (job->driver, render_target, TRUE); @@ -2365,14 +2339,7 @@ gsk_gl_render_job_visit_blurred_inset_shadow_node (GskGLRenderJob *job, { GskRoundedRect node_clip; - gsk_gl_render_job_transform_bounds (job, &node_outline->bounds, &node_clip.bounds); - - for (guint i = 0; i < 4; i ++) - { - node_clip.corner[i].width = node_outline->corner[i].width * scale_x; - node_clip.corner[i].height = node_outline->corner[i].height * scale_y; - } - + gsk_gl_render_job_translate_rounded_rect (job, node_outline, &node_clip); gsk_gl_render_job_push_clip (job, &node_clip); } @@ -2422,7 +2389,7 @@ gsk_gl_render_job_visit_unblurred_outset_shadow_node (GskGLRenderJob *job, rgba_to_half (gsk_outset_shadow_node_get_color (node), color); - gsk_gl_render_job_transform_rounded_rect (job, outline, &transformed_outline); + gsk_gl_render_job_translate_rounded_rect (job, outline, &transformed_outline); gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, unblurred_outset_shadow)); gsk_gl_program_set_uniform_rounded_rect (job->current_program, @@ -2615,8 +2582,8 @@ gsk_gl_render_job_visit_blurred_outset_shadow_node (GskGLRenderJob *job, &offscreen, texture_width, texture_height, - blur_radius * scale_x, - blur_radius * scale_y); + blur_radius * fabs (scale_x), + blur_radius * fabs (scale_y)); gsk_gl_shadow_library_insert (job->driver->shadows_library, &scaled_outline, @@ -2630,7 +2597,7 @@ gsk_gl_render_job_visit_blurred_outset_shadow_node (GskGLRenderJob *job, blurred_texture_id = cached_tid; } - gsk_gl_render_job_transform_rounded_rect (job, outline, &transformed_outline); + gsk_gl_render_job_translate_rounded_rect (job, outline, &transformed_outline); if (!do_slicing) { @@ -2866,8 +2833,12 @@ gsk_gl_render_job_visit_cross_fade_node (GskGLRenderJob *job, offscreen_end.reset_clip = TRUE; offscreen_end.bounds = &node->bounds; + gsk_gl_render_job_set_modelview (job, NULL); + if (!gsk_gl_render_job_visit_node_with_offscreen (job, start_node, &offscreen_start)) { + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_visit_node (job, end_node); return; } @@ -2876,12 +2847,18 @@ gsk_gl_render_job_visit_cross_fade_node (GskGLRenderJob *job, if (!gsk_gl_render_job_visit_node_with_offscreen (job, end_node, &offscreen_end)) { - float prev_alpha = gsk_gl_render_job_set_alpha (job, job->alpha * progress); + float prev_alpha; + + gsk_gl_render_job_pop_modelview (job); + + prev_alpha = gsk_gl_render_job_set_alpha (job, job->alpha * progress); gsk_gl_render_job_visit_node (job, start_node); gsk_gl_render_job_set_alpha (job, prev_alpha); return; } + gsk_gl_render_job_pop_modelview (job); + g_assert (offscreen_end.texture_id); gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, cross_fade)); @@ -3275,10 +3252,14 @@ gsk_gl_render_job_visit_blend_node (GskGLRenderJob *job, bottom_offscreen.force_offscreen = TRUE; bottom_offscreen.reset_clip = TRUE; + gsk_gl_render_job_set_modelview (job, NULL); + /* TODO: We create 2 textures here as big as the blend node, but both the * start and the end node might be a lot smaller than that. */ if (!gsk_gl_render_job_visit_node_with_offscreen (job, bottom_child, &bottom_offscreen)) { + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_visit_node (job, top_child); return; } @@ -3287,6 +3268,8 @@ gsk_gl_render_job_visit_blend_node (GskGLRenderJob *job, if (!gsk_gl_render_job_visit_node_with_offscreen (job, top_child, &top_offscreen)) { + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit)); gsk_gl_program_set_uniform_texture (job->current_program, UNIFORM_SHARED_SOURCE, 0, @@ -3300,6 +3283,8 @@ gsk_gl_render_job_visit_blend_node (GskGLRenderJob *job, g_assert (top_offscreen.was_offscreen); + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blend)); gsk_gl_program_set_uniform_texture (job->current_program, UNIFORM_SHARED_SOURCE, 0, @@ -3336,11 +3321,14 @@ gsk_gl_render_job_visit_mask_node (GskGLRenderJob *job, mask_offscreen.reset_clip = TRUE; mask_offscreen.do_not_cache = TRUE; + gsk_gl_render_job_set_modelview (job, NULL); + /* TODO: We create 2 textures here as big as the mask node, but both * nodes might be a lot smaller than that. */ if (!gsk_gl_render_job_visit_node_with_offscreen (job, source, &source_offscreen)) { + gsk_gl_render_job_pop_modelview (job); gsk_gl_render_job_visit_node (job, source); return; } @@ -3349,11 +3337,14 @@ gsk_gl_render_job_visit_mask_node (GskGLRenderJob *job, if (!gsk_gl_render_job_visit_node_with_offscreen (job, mask, &mask_offscreen)) { + gsk_gl_render_job_pop_modelview (job); return; } g_assert (mask_offscreen.was_offscreen); + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, mask)); gsk_gl_program_set_uniform_texture (job->current_program, UNIFORM_SHARED_SOURCE, 0, @@ -3587,8 +3578,8 @@ gsk_gl_render_job_visit_texture (GskGLRenderJob *job, float scale_y = bounds->size.height / texture->height; gboolean use_mipmap; - use_mipmap = (scale_x * job->scale_x) < 0.5 || - (scale_y * job->scale_y) < 0.5; + use_mipmap = (scale_x * fabs (job->scale_x)) < 0.5 || + (scale_y * fabs (job->scale_y)) < 0.5; if G_LIKELY (texture->width <= max_texture_size && texture->height <= max_texture_size) @@ -4120,7 +4111,7 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, } if (gsk_render_node_get_node_type (node) == GSK_TEXTURE_NODE && - offscreen->force_offscreen == FALSE) + !offscreen->force_offscreen) { GdkTexture *texture = gsk_texture_node_get_texture (node); gsk_gl_render_job_upload_texture (job, texture, FALSE, offscreen); @@ -4138,6 +4129,7 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, gboolean flipped_x = job->scale_x < 0; gboolean flipped_y = job->scale_y < 0; graphene_rect_t viewport; + gboolean reset_clip = FALSE; if (flipped_x || flipped_y) { @@ -4185,6 +4177,7 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, gsk_gl_render_job_push_modelview (job, transform); gsk_transform_unref (transform); gsk_gl_render_job_transform_bounds (job, offscreen->bounds, &viewport); + graphene_rect_scale (&viewport, downscale_x, downscale_y, &viewport); } if (downscale_x == 1) @@ -4268,11 +4261,25 @@ gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); if (offscreen->reset_clip) - gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (job->viewport)); + { + gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (job->viewport)); + reset_clip = TRUE; + } + else if (flipped_x || flipped_y || downscale_x != 1 || downscale_y != 1) + { + GskRoundedRect new_clip; + float scale_x = flipped_x ? - downscale_x : downscale_x; + float scale_y = flipped_y ? - downscale_y : downscale_y; + + graphene_rect_scale (&job->current_clip->rect.bounds, scale_x, scale_y, &new_clip.bounds); + rounded_rect_scale_corners (&job->current_clip->rect, &new_clip, scale_x, scale_y); + gsk_gl_render_job_push_clip (job, &new_clip); + reset_clip = TRUE; + } gsk_gl_render_job_visit_node (job, node); - if (offscreen->reset_clip) + if (reset_clip) gsk_gl_render_job_pop_clip (job); if (downscale_x != 1 || downscale_y != 1) diff --git a/gsk/gl/resources/border.glsl b/gsk/gl/resources/border.glsl index b8653bad49..0fa3203887 100644 --- a/gsk/gl/resources/border.glsl +++ b/gsk/gl/resources/border.glsl @@ -19,6 +19,9 @@ void main() { gsk_rounded_rect_transform(outside, u_modelview); gsk_rounded_rect_transform(inside, u_modelview); + gsk_rounded_rect_normalize(outside); + gsk_rounded_rect_normalize(inside); + gsk_rounded_rect_encode(outside, transformed_outside_outline); gsk_rounded_rect_encode(inside, transformed_inside_outline); } @@ -34,10 +37,9 @@ _IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline; void main() { vec2 frag = gsk_get_frag_coord(); - - float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) - - gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag), - 0.0, 1.0); + float outer_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag); + float inner_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag); + float alpha = clamp(outer_coverage - inner_coverage, 0.0, 1.0); gskSetScaledOutputColor(final_color, alpha); } diff --git a/gsk/gl/resources/filled_border.glsl b/gsk/gl/resources/filled_border.glsl index d503dd0500..23af3aa89c 100644 --- a/gsk/gl/resources/filled_border.glsl +++ b/gsk/gl/resources/filled_border.glsl @@ -21,6 +21,9 @@ void main() { gsk_rounded_rect_transform(outside, u_modelview); gsk_rounded_rect_transform(inside, u_modelview); + gsk_rounded_rect_normalize(outside); + gsk_rounded_rect_normalize(inside); + gsk_rounded_rect_encode(outside, transformed_outside_outline); gsk_rounded_rect_encode(inside, transformed_inside_outline); } diff --git a/gsk/gl/resources/inset_shadow.glsl b/gsk/gl/resources/inset_shadow.glsl index f052a08580..83d4287f13 100644 --- a/gsk/gl/resources/inset_shadow.glsl +++ b/gsk/gl/resources/inset_shadow.glsl @@ -22,6 +22,9 @@ void main() { gsk_rounded_rect_transform(outside, u_modelview); gsk_rounded_rect_transform(inside, u_modelview); + gsk_rounded_rect_normalize(outside); + gsk_rounded_rect_normalize(inside); + gsk_rounded_rect_encode(outside, transformed_outside_outline); gsk_rounded_rect_encode(inside, transformed_inside_outline); } diff --git a/gsk/gl/resources/outset_shadow.glsl b/gsk/gl/resources/outset_shadow.glsl index 44b05aa152..fac86c0093 100644 --- a/gsk/gl/resources/outset_shadow.glsl +++ b/gsk/gl/resources/outset_shadow.glsl @@ -15,6 +15,7 @@ void main() { GskRoundedRect outline = gsk_create_rect(u_outline_rect); gsk_rounded_rect_transform(outline, u_modelview); + gsk_rounded_rect_normalize(outline); gsk_rounded_rect_encode(outline, transformed_outline); } diff --git a/gsk/gl/resources/preamble.fs.glsl b/gsk/gl/resources/preamble.fs.glsl index c1c5a954ee..0c366bd847 100644 --- a/gsk/gl/resources/preamble.fs.glsl +++ b/gsk/gl/resources/preamble.fs.glsl @@ -16,11 +16,15 @@ _IN_ vec2 vUv; GskRoundedRect gsk_decode_rect(_GSK_ROUNDED_RECT_UNIFORM_ r) { + GskRoundedRect rect; #if defined(GSK_GLES) || defined(GSK_LEGACY) - return GskRoundedRect(r[0], r[1], r[2]); + rect = GskRoundedRect(r[0], r[1], r[2]); #else - return r; + rect = r; #endif + gsk_rounded_rect_normalize (rect); + + return rect; } float diff --git a/gsk/gl/resources/preamble.glsl b/gsk/gl/resources/preamble.glsl index 5f5d2b780a..46bebc14e0 100644 --- a/gsk/gl/resources/preamble.glsl +++ b/gsk/gl/resources/preamble.glsl @@ -22,6 +22,55 @@ struct GskRoundedRect vec4 corner_points2; // xy = bottom right, zw = bottom left }; +void gsk_rounded_rect_normalize(inout GskRoundedRect r) +{ + if (r.bounds.x > r.bounds.z) + { + float t = r.bounds.x; + r.bounds.x = r.bounds.z; + r.bounds.z = t; + + vec2 c = r.corner_points1.xy; + r.corner_points1.xy = r.corner_points1.zw; + r.corner_points1.zw = c; + + c = r.corner_points2.xy; + r.corner_points2.xy = r.corner_points2.zw; + r.corner_points2.zw = c; + } + + if (r.bounds.y > r.bounds.w) + { + float t = r.bounds.y; + r.bounds.y = r.bounds.w; + r.bounds.w = t; + + vec2 c = r.corner_points1.xy; + r.corner_points1.xy = r.corner_points2.xy; + r.corner_points2.xy = c; + + c = r.corner_points1.zw; + r.corner_points1.zw = r.corner_points2.zw; + r.corner_points2.zw = c; + } +} + +void gsk_bounds_normalize (inout vec4 bounds) +{ + if (bounds.x > bounds.z) + { + float t = bounds.x; + bounds.x = bounds.z; + bounds.z = t; + } + if (bounds.y > bounds.w) + { + float t = bounds.y; + bounds.y = bounds.w; + bounds.w = t; + } +} + // Transform from a C GskRoundedRect to what we need. GskRoundedRect gsk_create_rect(vec4[3] data) @@ -33,13 +82,21 @@ gsk_create_rect(vec4[3] data) vec4 corner_points2 = vec4(bounds.zw + (data[2].xy * vec2(-1, -1)), bounds.xw + vec2(data[2].zw * vec2(1, -1))); - return GskRoundedRect(bounds, corner_points1, corner_points2); + GskRoundedRect rect = GskRoundedRect(bounds, corner_points1, corner_points2); + + gsk_rounded_rect_normalize (rect); + + return rect; } vec4 gsk_get_bounds(vec4[3] data) { - return vec4(data[0].xy, data[0].xy + data[0].zw); + vec4 bounds = vec4(data[0].xy, data[0].xy + data[0].zw); + + gsk_bounds_normalize (bounds); + + return bounds; } vec4 gsk_premultiply(vec4 c) { diff --git a/gsk/gskroundedrect.c b/gsk/gskroundedrect.c index 42df2bf06c..7c0733fbe9 100644 --- a/gsk/gskroundedrect.c +++ b/gsk/gskroundedrect.c @@ -519,8 +519,174 @@ gsk_rounded_rect_intersects_rect (const GskRoundedRect *self, gsk_rounded_rect_locate_point (self, &GRAPHENE_POINT_INIT (rect->origin.x, rect->origin.y + rect->size.height)) == OUTSIDE_TOP_RIGHT || gsk_rounded_rect_locate_point (self, &GRAPHENE_POINT_INIT (rect->origin.x + rect->size.width, rect->origin.y + rect->size.height)) == OUTSIDE_TOP_LEFT) return FALSE; +return TRUE; +} - return TRUE; +#define rect_point0(r) ((r)->origin) +#define rect_point1(r) (GRAPHENE_POINT_INIT ((r)->origin.x + (r)->size.width, (r)->origin.y)) +#define rect_point2(r) (GRAPHENE_POINT_INIT ((r)->origin.x + (r)->size.width, (r)->origin.y + (r)->size.height)) +#define rect_point3(r) (GRAPHENE_POINT_INIT ((r)->origin.x, (r)->origin.y + (r)->size.height)) + +#define rounded_rect_corner0(r) \ + (GRAPHENE_RECT_INIT((r)->bounds.origin.x, \ + (r)->bounds.origin.y, \ + (r)->corner[0].width, (r)->corner[0].height)) +#define rounded_rect_corner1(r) \ + (GRAPHENE_RECT_INIT((r)->bounds.origin.x + (r)->bounds.size.width - (r)->corner[1].width, \ + (r)->bounds.origin.y, \ + (r)->corner[1].width, (r)->corner[1].height)) +#define rounded_rect_corner2(r) \ + (GRAPHENE_RECT_INIT((r)->bounds.origin.x + (r)->bounds.size.width - (r)->corner[2].width, \ + (r)->bounds.origin.y + (r)->bounds.size.height - (r)->corner[2].height, \ + (r)->corner[2].width, (r)->corner[2].height)) +#define rounded_rect_corner3(r) \ + (GRAPHENE_RECT_INIT((r)->bounds.origin.x, \ + (r)->bounds.origin.y + (r)->bounds.size.height - (r)->corner[3].height, \ + (r)->corner[3].width, (r)->corner[3].height)) + +enum { + BELOW, + INNER, + ABOVE +}; + +static inline void +classify_point (const graphene_point_t *p, const graphene_rect_t *rect, int *px, int *py) +{ + if (p->x <= rect->origin.x) + *px = BELOW; + else if (p->x >= rect->origin.x + rect->size.width) + *px = ABOVE; + else + *px = INNER; + + if (p->y <= rect->origin.y) + *py = BELOW; + else if (p->y >= rect->origin.y + rect->size.height) + *py = ABOVE; + else + *py = INNER; +} + +GskRoundedRectIntersection +gsk_rounded_rect_intersect_with_rect (const GskRoundedRect *self, + const graphene_rect_t *rect, + GskRoundedRect *result) +{ + int px, py, qx, qy; + + if (!graphene_rect_intersection (&self->bounds, rect, &result->bounds)) + return GSK_INTERSECTION_EMPTY; + + classify_point (&rect_point0 (rect), &rounded_rect_corner0 (self), &px, &py); + + if (px == BELOW && py == BELOW) + { + classify_point (&rect_point2 (rect), &rounded_rect_corner0 (self), &qx, &qy); + + if (qx == BELOW || qy == BELOW) + return GSK_INTERSECTION_EMPTY; + else if (qx == INNER && qy == INNER && + gsk_rounded_rect_locate_point (self, &rect_point2 (rect)) != INSIDE) + return GSK_INTERSECTION_EMPTY; + else if (qx == ABOVE && qy == ABOVE) + result->corner[0] = self->corner[0]; + else + return GSK_INTERSECTION_NOT_REPRESENTABLE; + } + else if ((px == INNER || py == INNER) && + gsk_rounded_rect_locate_point (self, &rect_point0 (rect)) != INSIDE) + { + if (gsk_rounded_rect_locate_point (self, &rect_point2 (rect)) == OUTSIDE_TOP_LEFT) + return GSK_INTERSECTION_EMPTY; + else + return GSK_INTERSECTION_NOT_REPRESENTABLE; + } + else + result->corner[0].width = result->corner[0].height = 0; + + classify_point (&rect_point1 (rect), &rounded_rect_corner1 (self), &px, &py); + + if (px == ABOVE && py == BELOW) + { + classify_point (&rect_point3 (rect), &rounded_rect_corner1 (self), &qx, &qy); + + if (qx == ABOVE || qy == BELOW) + return GSK_INTERSECTION_EMPTY; + else if (qx == INNER && qy == INNER && + gsk_rounded_rect_locate_point (self, &rect_point3 (rect)) != INSIDE) + return GSK_INTERSECTION_EMPTY; + else if (qx == BELOW && qy == ABOVE) + result->corner[1] = self->corner[1]; + else + return GSK_INTERSECTION_NOT_REPRESENTABLE; + } + else if ((px == INNER || py == INNER) && + gsk_rounded_rect_locate_point (self, &rect_point1 (rect)) != INSIDE) + { + if (gsk_rounded_rect_locate_point (self, &rect_point3 (rect)) == OUTSIDE_TOP_RIGHT) + return GSK_INTERSECTION_EMPTY; + else + return GSK_INTERSECTION_NOT_REPRESENTABLE; + } + else + result->corner[1].width = result->corner[1].height = 0; + + classify_point (&rect_point2 (rect), &rounded_rect_corner2 (self), &px, &py); + + if (px == ABOVE && py == ABOVE) + { + classify_point (&rect_point0 (rect), &rounded_rect_corner2 (self), &qx, &qy); + + if (qx == ABOVE || qy == ABOVE) + return GSK_INTERSECTION_EMPTY; + else if (qx == INNER && qy == INNER && + gsk_rounded_rect_locate_point (self, &rect_point0 (rect)) != INSIDE) + return GSK_INTERSECTION_EMPTY; + else if (qx == BELOW && qy == BELOW) + result->corner[2] = self->corner[2]; + else + return GSK_INTERSECTION_NOT_REPRESENTABLE; + } + else if ((px == INNER || py == INNER) && + gsk_rounded_rect_locate_point (self, &rect_point2 (rect)) != INSIDE) + { + if (gsk_rounded_rect_locate_point (self, &rect_point0 (rect)) == OUTSIDE_BOTTOM_RIGHT) + return GSK_INTERSECTION_EMPTY; + else + return GSK_INTERSECTION_NOT_REPRESENTABLE; + } + else + result->corner[2].width = result->corner[2].height = 0; + + classify_point (&rect_point3 (rect), &rounded_rect_corner3 (self), &px, &py); + + if (px == BELOW && py == ABOVE) + { + classify_point (&rect_point1 (rect), &rounded_rect_corner3 (self), &qx, &qy); + + if (qx == BELOW || qy == ABOVE) + return GSK_INTERSECTION_EMPTY; + else if (qx == INNER && qy == INNER && + gsk_rounded_rect_locate_point (self, &rect_point1 (rect)) != INSIDE) + return GSK_INTERSECTION_EMPTY; + else if (qx == ABOVE && qy == BELOW) + result->corner[3] = self->corner[3]; + else + return GSK_INTERSECTION_NOT_REPRESENTABLE; + } + else if ((px == INNER || py == INNER) && + gsk_rounded_rect_locate_point (self, &rect_point3 (rect)) != INSIDE) + { + if (gsk_rounded_rect_locate_point (self, &rect_point1 (rect)) == OUTSIDE_BOTTOM_LEFT) + return GSK_INTERSECTION_EMPTY; + else + return GSK_INTERSECTION_NOT_REPRESENTABLE; + } + else + result->corner[3].width = result->corner[3].height = 0; + + return GSK_INTERSECTION_NONEMPTY; } static void @@ -565,7 +731,7 @@ gsk_rounded_rect_path (const GskRoundedRect *self, self->corner[GSK_CORNER_TOP_LEFT].width, self->corner[GSK_CORNER_TOP_LEFT].height, G_PI, 3 * G_PI_2); - _cairo_ellipsis (cr, + _cairo_ellipsis (cr, self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_TOP_RIGHT].width, self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height, self->corner[GSK_CORNER_TOP_RIGHT].width, @@ -649,5 +815,4 @@ gsk_rounded_rect_to_string (const GskRoundedRect *self) self->corner[2].height, self->corner[3].width, self->corner[3].height); - } diff --git a/gsk/gskroundedrectprivate.h b/gsk/gskroundedrectprivate.h index 5be1a4ab0c..b9afb754cc 100644 --- a/gsk/gskroundedrectprivate.h +++ b/gsk/gskroundedrectprivate.h @@ -34,6 +34,16 @@ gboolean gsk_rounded_rect_equal (gconstpointer gconstpointer rect2) G_GNUC_PURE; char * gsk_rounded_rect_to_string (const GskRoundedRect *self) G_GNUC_MALLOC; +typedef enum { + GSK_INTERSECTION_EMPTY, + GSK_INTERSECTION_NONEMPTY, + GSK_INTERSECTION_NOT_REPRESENTABLE +} GskRoundedRectIntersection; + +GskRoundedRectIntersection gsk_rounded_rect_intersect_with_rect (const GskRoundedRect *self, + const graphene_rect_t *rect, + GskRoundedRect *result) G_GNUC_PURE; + G_END_DECLS diff --git a/testsuite/gsk/compare-render.c b/testsuite/gsk/compare-render.c index 919957e70d..4e97781cbe 100644 --- a/testsuite/gsk/compare-render.c +++ b/testsuite/gsk/compare-render.c @@ -5,6 +5,9 @@ #include "../reftests/reftest-compare.h" static char *arg_output_dir = NULL; +static gboolean flip = FALSE; +static gboolean rotate = FALSE; +static gboolean repeat = FALSE; static const char * get_output_dir (void) @@ -112,6 +115,20 @@ save_image (GdkTexture *texture, g_free (filename); } +static void +save_node (GskRenderNode *node, + const char *test_name, + const char *extension) +{ + char *filename = get_output_file (test_name, ".node", extension); + gboolean result; + + g_print ("Storing modified nodes at %s\n", filename); + result = gsk_render_node_write_to_file (node, filename, NULL); + g_assert_true (result); + g_free (filename); +} + static void deserialize_error_func (const GskParseLocation *start, const GskParseLocation *end, @@ -136,11 +153,39 @@ deserialize_error_func (const GskParseLocation *start, } static const GOptionEntry options[] = { - { "output", 0, 0, G_OPTION_ARG_FILENAME, &arg_output_dir, - "Directory to save image files to", "DIR" }, + { "output", 0, 0, G_OPTION_ARG_FILENAME, &arg_output_dir, "Directory to save image files to", "DIR" }, + { "flip", 0, 0, G_OPTION_ARG_NONE, &flip, "Do flipped test", NULL }, + { "rotate", 0, 0, G_OPTION_ARG_NONE, &rotate, "Do rotated test", NULL }, + { "repeat", 0, 0, G_OPTION_ARG_NONE, &repeat, "Do repeat test", NULL }, { NULL } }; +static GskRenderNode * +load_node_file (const char *node_file) +{ + GBytes *bytes; + gsize len; + char *contents; + GError *error = NULL; + GskRenderNode *node; + + if (!g_file_get_contents (node_file, &contents, &len, &error)) + { + g_print ("Could not open node file: %s\n", error->message); + g_clear_error (&error); + return NULL; + } + + bytes = g_bytes_new_take (contents, len); + node = gsk_render_node_deserialize (bytes, deserialize_error_func, NULL); + g_bytes_unref (bytes); + + g_assert_no_error (error); + g_assert_nonnull (node); + + return node; +} + /* * Non-option arguments: * 1) .node file to compare @@ -159,6 +204,7 @@ main (int argc, char **argv) gboolean success = TRUE; GError *error = NULL; GOptionContext *context; + GdkTexture *diff_texture; (g_test_init) (&argc, &argv, NULL); @@ -185,64 +231,182 @@ main (int argc, char **argv) node_file = argv[1]; png_file = argv[2]; - window = gdk_surface_new_toplevel (gdk_display_get_default()); - renderer = gsk_renderer_new_for_surface (window); - g_print ("Node file: '%s'\n", node_file); g_print ("PNG file: '%s'\n", png_file); + window = gdk_surface_new_toplevel (gdk_display_get_default()); + renderer = gsk_renderer_new_for_surface (window); + /* Load the render node from the given .node file */ - { - GBytes *bytes; - gsize len; - char *contents; - - if (!g_file_get_contents (node_file, &contents, &len, &error)) - { - g_print ("Could not open node file: %s\n", error->message); - g_clear_error (&error); - return 1; - } - - bytes = g_bytes_new_take (contents, len); - node = gsk_render_node_deserialize (bytes, deserialize_error_func, &success); - g_bytes_unref (bytes); - - g_assert_no_error (error); - g_assert_nonnull (node); - } + node = load_node_file (node_file); + if (!node) + return 1; /* Render the .node file and download to cairo surface */ rendered_texture = gsk_renderer_render_texture (renderer, node, NULL); g_assert_nonnull (rendered_texture); + save_image (rendered_texture, node_file, ".out.png"); + /* Load the given reference png file */ reference_texture = gdk_texture_new_from_filename (png_file, &error); if (reference_texture == NULL) { g_print ("Error loading reference surface: %s\n", error->message); g_clear_error (&error); + save_image (rendered_texture, node_file, ".out.png"); + return 0; + } + + /* Now compare the two */ + diff_texture = reftest_compare_textures (rendered_texture, reference_texture); + if (diff_texture) + { + save_image (diff_texture, node_file, ".diff.png"); + g_object_unref (diff_texture); success = FALSE; } - else - { - GdkTexture *diff_texture; - /* Now compare the two */ + g_clear_object (&reference_texture); + g_clear_object (&rendered_texture); + + if (flip) + { + GskRenderNode *node2; + GdkPixbuf *pixbuf, *pixbuf2; + + node2 = gsk_transform_node_new (node, gsk_transform_scale (NULL, -1, 1)); + rendered_texture = gsk_renderer_render_texture (renderer, node2, NULL); + + save_image (rendered_texture, node_file, "-flipped.out.png"); + + pixbuf = gdk_pixbuf_new_from_file (png_file, &error); + pixbuf2 = gdk_pixbuf_flip (pixbuf, TRUE); + reference_texture = gdk_texture_new_for_pixbuf (pixbuf2); + g_object_unref (pixbuf2); + g_object_unref (pixbuf); + + save_image (reference_texture, node_file, "-flipped.ref.png"); + diff_texture = reftest_compare_textures (rendered_texture, reference_texture); if (diff_texture) { - save_image (diff_texture, node_file, ".diff.png"); + save_node (node2, node_file, "-flipped.node"); + save_image (diff_texture, node_file, "-flipped.diff.png"); g_object_unref (diff_texture); success = FALSE; } + + g_clear_object (&rendered_texture); + g_clear_object (&reference_texture); + gsk_render_node_unref (node2); } - save_image (rendered_texture, node_file, ".out.png"); + if (repeat) + { + GskRenderNode *node2; + GdkPixbuf *pixbuf, *pixbuf2, *pixbuf3; + int width, height; + graphene_rect_t node_bounds; + graphene_rect_t bounds; + float offset_x, offset_y; - g_object_unref (reference_texture); - g_object_unref (rendered_texture); + gsk_render_node_get_bounds (node, &node_bounds); + + if (node_bounds.size.width > 32768. || node_bounds.size.height > 32768.) + { + g_print ("Avoiding repeat test that exceeds cairo image surface dimensions"); + exit (77); + } + + bounds.origin.x = 0.; + bounds.origin.y = 0.; + bounds.size.width = 2 * node_bounds.size.width; + bounds.size.height = 2 * node_bounds.size.height; + + offset_x = floorf (fmodf (node_bounds.origin.x, node_bounds.size.width)); + offset_y = floorf (fmodf (node_bounds.origin.y, node_bounds.size.height)); + if (offset_x < 0) + offset_x += node_bounds.size.width; + if (offset_y < 0) + offset_y += node_bounds.size.height; + + node2 = gsk_repeat_node_new (&bounds, node, &node_bounds); + rendered_texture = gsk_renderer_render_texture (renderer, node2, NULL); + save_image (rendered_texture, node_file, "-repeated.out.png"); + + pixbuf = gdk_pixbuf_new_from_file (png_file, &error); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + pixbuf2 = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf), + gdk_pixbuf_get_has_alpha (pixbuf), + gdk_pixbuf_get_bits_per_sample (pixbuf), + width * 3, + height * 3); + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + gdk_pixbuf_copy_area (pixbuf, 0, 0, width, height, pixbuf2, i * width, j * height); + + pixbuf3 = gdk_pixbuf_new_subpixbuf (pixbuf2, width - (int) offset_x, height - (int) offset_y, 2 * width, 2 * height); + + reference_texture = gdk_texture_new_for_pixbuf (pixbuf3); + + g_object_unref (pixbuf3); + g_object_unref (pixbuf2); + g_object_unref (pixbuf); + + save_image (reference_texture, node_file, "-repeated.ref.png"); + + diff_texture = reftest_compare_textures (rendered_texture, reference_texture); + + if (diff_texture) + { + save_node (node2, node_file, "-repeated.node"); + save_image (diff_texture, node_file, "-repeated.diff.png"); + g_object_unref (diff_texture); + success = FALSE; + } + + g_clear_object (&rendered_texture); + g_clear_object (&reference_texture); + gsk_render_node_unref (node2); + } + + if (rotate) + { + GskRenderNode *node2; + GdkPixbuf *pixbuf, *pixbuf2; + + node2 = gsk_transform_node_new (node, gsk_transform_rotate (NULL, 90)); + rendered_texture = gsk_renderer_render_texture (renderer, node2, NULL); + + save_image (rendered_texture, node_file, "-rotated.out.png"); + + pixbuf = gdk_pixbuf_new_from_file (png_file, &error); + pixbuf2 = gdk_pixbuf_rotate_simple (pixbuf, GDK_PIXBUF_ROTATE_CLOCKWISE); + reference_texture = gdk_texture_new_for_pixbuf (pixbuf2); + g_object_unref (pixbuf2); + g_object_unref (pixbuf); + + save_image (reference_texture, node_file, "-rotated.ref.png"); + + diff_texture = reftest_compare_textures (rendered_texture, reference_texture); + + if (diff_texture) + { + save_node (node2, node_file, "-rotated.node"); + save_image (diff_texture, node_file, "-rotated.diff.png"); + g_object_unref (diff_texture); + success = FALSE; + } + + g_clear_object (&rendered_texture); + g_clear_object (&reference_texture); + gsk_render_node_unref (node2); + } gsk_render_node_unref (node); diff --git a/testsuite/gsk/compare/big-glyph-wrapped.png b/testsuite/gsk/compare/big-glyph-wrapped.png new file mode 100644 index 0000000000..c9d502f7fa Binary files /dev/null and b/testsuite/gsk/compare/big-glyph-wrapped.png differ diff --git a/testsuite/gsk/compare/blend-in-rotate.node b/testsuite/gsk/compare/blend-in-rotate.node new file mode 100644 index 0000000000..4e70392013 --- /dev/null +++ b/testsuite/gsk/compare/blend-in-rotate.node @@ -0,0 +1,13 @@ +transform { + transform: rotate(90); + child: blend { + bottom: color { + bounds: 0 0 100 100; + color: rgb(255,0,0); + } + top: color { + bounds: 50 50 100 100; + color: rgb(0,0,255); + } + } +} diff --git a/testsuite/gsk/compare/blend-in-rotate.png b/testsuite/gsk/compare/blend-in-rotate.png new file mode 100644 index 0000000000..4a684f3b11 Binary files /dev/null and b/testsuite/gsk/compare/blend-in-rotate.png differ diff --git a/testsuite/gsk/compare/blend-modes.node b/testsuite/gsk/compare/blend-modes.node new file mode 100644 index 0000000000..b5e7e3eeb6 --- /dev/null +++ b/testsuite/gsk/compare/blend-modes.node @@ -0,0 +1,23 @@ +blend { + mode: difference; + bottom: color { + bounds: 0 0 100 100; + color: red; + } + top: color { + bounds: 50 50 100 100; + color: blue; + } +} + +blend { + mode: normal; + bottom: color { + bounds: 150 0 100 100; + color: red; + } + top: color { + bounds: 200 50 100 100; + color: blue; + } +} diff --git a/testsuite/gsk/compare/blend-modes.png b/testsuite/gsk/compare/blend-modes.png new file mode 100644 index 0000000000..8ae7de33f6 Binary files /dev/null and b/testsuite/gsk/compare/blend-modes.png differ diff --git a/testsuite/gsk/compare/border-one-rounded.node b/testsuite/gsk/compare/border-one-rounded.node new file mode 100644 index 0000000000..53ad843113 --- /dev/null +++ b/testsuite/gsk/compare/border-one-rounded.node @@ -0,0 +1,23 @@ +border { + outline: 0 0 50 50 / 0 0 0 20; +} +color { + bounds: 0 29 4 11; + color: rgb(255,192,203); +} +color { + bounds: 2 40 4 4; + color: rgb(255,192,203); +} +color { + bounds: 4 42 4 4; + color: rgb(255,192,203); +} +color { + bounds: 6 44 4 4; + color: rgb(255,192,203); +} +color { + bounds: 9 46 11 4; + color: rgb(255,192,203); +} diff --git a/testsuite/gsk/compare/border-one-rounded.png b/testsuite/gsk/compare/border-one-rounded.png new file mode 100644 index 0000000000..fe75324637 Binary files /dev/null and b/testsuite/gsk/compare/border-one-rounded.png differ diff --git a/testsuite/gsk/compare/cross-fade-in-rotate.node b/testsuite/gsk/compare/cross-fade-in-rotate.node new file mode 100644 index 0000000000..d4714b65c4 --- /dev/null +++ b/testsuite/gsk/compare/cross-fade-in-rotate.node @@ -0,0 +1,14 @@ +transform { + transform: rotate(90); + child: cross-fade { + progress: 0; + start: color { + bounds: 25 0 25 50; + color: rgb(255,0,0); + } + end: color { + bounds: 0 0 25 50; + color: rgb(0,0,0); + } + } +} diff --git a/testsuite/gsk/compare/cross-fade-in-rotate.png b/testsuite/gsk/compare/cross-fade-in-rotate.png new file mode 100644 index 0000000000..8f36f35fda Binary files /dev/null and b/testsuite/gsk/compare/cross-fade-in-rotate.png differ diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build index cfc1338aae..7e738cd097 100644 --- a/testsuite/gsk/meson.build +++ b/testsuite/gsk/meson.build @@ -15,9 +15,12 @@ compare_render_tests = [ 'big-checkerboard-scaled-down-nearest', 'big-checkerboard-scaled-down2', 'blend-difference', + 'blend-modes', 'blend-invisible-child', + 'blend-in-rotate', 'blend-normal', 'border-bottom-right', + 'border-one-rounded', 'borders-rotated', 'borders-scaled', 'clip-coordinates-2d', @@ -32,6 +35,7 @@ compare_render_tests = [ 'color-matrix-identity', 'color-matrix-parsing', 'cross-fade-in-opacity', + 'cross-fade-in-rotate', 'css-background', 'empty-blend', 'empty-blur', @@ -44,8 +48,7 @@ compare_render_tests = [ 'empty-cross-fade', 'empty-debug', 'empty-inset-shadow', -# this test fails for some of the backends -# 'empty-linear-gradient', + 'empty-linear-gradient', 'empty-mask', 'empty-opacity', 'empty-outset-shadow', @@ -74,7 +77,6 @@ compare_render_tests = [ 'repeat-no-repeat', 'repeat-negative-coords', 'repeat-texture', - 'rounded-clip-in-clip-3d', # not really 3d, but cairo fails it 'scale-textures-negative-ngl', 'scale-up-down', 'scaled-cairo', @@ -94,6 +96,7 @@ informative_render_tests = [ 'big-glyph', 'empty-text', 'huge-glyph', + 'rounded-clip-in-clip-3d', # both cairo and gl fail this one ] renderers = [ @@ -104,10 +107,16 @@ renderers = [ compare_xfails = [ # Both tests fail because of some font rendering issue + 'empty-linear-gradient', 'unaligned-offscreen', 'upside-down-label-3d', # not really 3d, but cairo fails it ] +compare_xfails_small_texture = [ + 'big-checkerboard', + 'big-checkerboard-scaled-down' +] + foreach renderer : renderers foreach testname : compare_render_tests @@ -125,6 +134,10 @@ foreach renderer : renderers suites += 'failing' endif + if compare_xfails_small_texture.contains(testname) + suites += 'wayland_smalltexture_failing' + endif + if ((exclude_term == '' or not testname.contains(exclude_term)) and (renderer_name != 'broadway' or broadway_enabled)) test(renderer_name + ' ' + testname, compare_render, @@ -141,6 +154,51 @@ foreach renderer : renderers ], suite: suites, ) + test(renderer_name + ' ' + testname + ' flipped', compare_render, + args: [ + '--flip', + '--output', join_paths(meson.current_build_dir(), 'compare', renderer_name), + join_paths(meson.current_source_dir(), 'compare', testname + '.node'), + join_paths(meson.current_source_dir(), 'compare', testname + '.png'), + ], + env: [ + 'GSK_RENDERER=' + renderer_name, + 'GTK_A11Y=test', + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) + ], + suite: suites + [ 'gsk-compare-flipped-' + renderer_name ], + ) + test(renderer_name + ' ' + testname + ' repeated', compare_render, + args: [ + '--repeat', + '--output', join_paths(meson.current_build_dir(), 'compare', renderer_name), + join_paths(meson.current_source_dir(), 'compare', testname + '.node'), + join_paths(meson.current_source_dir(), 'compare', testname + '.png'), + ], + env: [ + 'GSK_RENDERER=' + renderer_name, + 'GTK_A11Y=test', + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) + ], + suite: suites + [ 'gsk-compare-repeated-' + renderer_name ], + ) + test(renderer_name + ' ' + testname + ' rotated', compare_render, + args: [ + '--rotate', + '--output', join_paths(meson.current_build_dir(), 'compare', renderer_name), + join_paths(meson.current_source_dir(), 'compare', testname + '.node'), + join_paths(meson.current_source_dir(), 'compare', testname + '.png'), + ], + env: [ + 'GSK_RENDERER=' + renderer_name, + 'GTK_A11Y=test', + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) + ], + suite: suites + [ 'gsk-compare-rotated-' + renderer_name ], + ) endif endforeach endforeach diff --git a/testsuite/gsk/rounded-rect.c b/testsuite/gsk/rounded-rect.c index 0127d1cb54..6ea7e7475b 100644 --- a/testsuite/gsk/rounded-rect.c +++ b/testsuite/gsk/rounded-rect.c @@ -160,6 +160,80 @@ test_to_float (void) g_assert_true (flt[10] == 9. && flt[11] == 11.); } +#define ROUNDED_RECT_INIT_FULL(x,y,w,h,w0,h0,w1,h1,w2,h2,w3,h3) \ + (GskRoundedRect) { .bounds = GRAPHENE_RECT_INIT (x, y, w, h), \ + .corner = { \ + GRAPHENE_SIZE_INIT (w0, h0), \ + GRAPHENE_SIZE_INIT (w1, h1), \ + GRAPHENE_SIZE_INIT (w2, h2), \ + GRAPHENE_SIZE_INIT (w3, h3), \ + }} + +#define ROUNDED_RECT_INIT(x,y,w,h,r) \ + ROUNDED_RECT_INIT_FULL (x, y, w, h, r, r, r, r, r, r, r, r) + +#define ROUNDED_RECT_INIT_UNIFORM(x,y,w,h,r1,r2,r3,r4) \ + ROUNDED_RECT_INIT_FULL (x, y, w, h, r1, r1, r2, r2, r3, r3, r4, r4) + + +static void +test_intersect_with_rect (void) +{ + struct { + GskRoundedRect rounded; + graphene_rect_t rect; + GskRoundedRect expected; + GskRoundedRectIntersection result; + } test[] = { + { ROUNDED_RECT_INIT (20, 50, 100, 100, 50), GRAPHENE_RECT_INIT (60, 80, 60, 70), + ROUNDED_RECT_INIT (0, 0, 0, 0, 0), GSK_INTERSECTION_NOT_REPRESENTABLE }, + { ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GRAPHENE_RECT_INIT (0, 0, 100, 100), + ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GSK_INTERSECTION_NONEMPTY }, + { ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GRAPHENE_RECT_INIT (0, 0, 80, 80), + ROUNDED_RECT_INIT_UNIFORM (0, 0, 80, 80, 10, 0, 0, 0), GSK_INTERSECTION_NONEMPTY }, + { ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GRAPHENE_RECT_INIT (10, 10, 80, 80), + ROUNDED_RECT_INIT (10, 10, 80, 80, 0), GSK_INTERSECTION_NONEMPTY }, + { ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GRAPHENE_RECT_INIT (10, 15, 100, 70), + ROUNDED_RECT_INIT (10, 15, 90, 70, 0), GSK_INTERSECTION_NONEMPTY }, + { ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GRAPHENE_RECT_INIT (110, 0, 10, 10), + ROUNDED_RECT_INIT (0, 0, 0, 0, 0), GSK_INTERSECTION_EMPTY }, + { ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GRAPHENE_RECT_INIT (5, 5, 90, 90), + ROUNDED_RECT_INIT (5, 5, 90, 90, 0), GSK_INTERSECTION_NONEMPTY }, + { ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GRAPHENE_RECT_INIT (1, 1, 1, 1), + ROUNDED_RECT_INIT (0, 0, 0, 0, 0), GSK_INTERSECTION_EMPTY }, + { ROUNDED_RECT_INIT (0, 0, 100, 100, 10), GRAPHENE_RECT_INIT (5, -5, 10, 20), + ROUNDED_RECT_INIT (0, 0, 0, 0, 0), GSK_INTERSECTION_NOT_REPRESENTABLE }, + { ROUNDED_RECT_INIT_UNIFORM (-200, 0, 200, 100, 0, 0, 0, 40), GRAPHENE_RECT_INIT (-200, 0, 160, 100), + ROUNDED_RECT_INIT_UNIFORM (-200, 0, 160, 100, 0, 0, 0, 40), GSK_INTERSECTION_NONEMPTY }, + }; + + for (unsigned int i = 0; i < G_N_ELEMENTS (test); i++) + { + GskRoundedRect out; + GskRoundedRectIntersection res; + + if (g_test_verbose ()) + g_test_message ("intersection test %u", i); + + memset (&out, 0, sizeof (GskRoundedRect)); + + res = gsk_rounded_rect_intersect_with_rect (&test[i].rounded, &test[i].rect, &out); + g_assert_true (res == test[i].result); + if (res == GSK_INTERSECTION_NONEMPTY) + { + if (!gsk_rounded_rect_equal (&out, &test[i].expected)) + { + char *s = gsk_rounded_rect_to_string (&test[i].expected); + char *s2 = gsk_rounded_rect_to_string (&out); + g_test_message ("expected %s, got %s\n", s, s2); + } + g_assert_true (gsk_rounded_rect_equal (&out, &test[i].expected)); + } + + g_assert_true ((res != GSK_INTERSECTION_EMPTY) == gsk_rounded_rect_intersects_rect (&test[i].rounded, &test[i].rect)); + } +} + int main (int argc, char *argv[]) @@ -171,6 +245,7 @@ main (int argc, g_test_add_func ("/rounded-rect/contains-point", test_contains_point); g_test_add_func ("/rounded-rect/is-circular", test_is_circular); g_test_add_func ("/rounded-rect/to-float", test_to_float); + g_test_add_func ("/rounded-rect/intersect-with-rect", test_intersect_with_rect); return g_test_run (); } diff --git a/testsuite/gsk/transform.c b/testsuite/gsk/transform.c index 1300dedf6a..855eb57f72 100644 --- a/testsuite/gsk/transform.c +++ b/testsuite/gsk/transform.c @@ -582,6 +582,14 @@ test_transform_bounds (void) t = gsk_transform_translate (t, &GRAPHENE_POINT_INIT (-50, -50)); gsk_transform_transform_bounds (t, &bounds, &out); g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(0, 0, 100, 100))); + + t = gsk_transform_scale (NULL, -1, 1); + gsk_transform_transform_bounds (t, &bounds, &out); + g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(-100, 0, 100, 100))); + + t = gsk_transform_scale (NULL, -1, -2); + gsk_transform_transform_bounds (t, &bounds, &out); + g_assert_true (graphene_rect_equal (&out, &GRAPHENE_RECT_INIT(-100, -200, 100, 200))); } #define DEG_TO_RAD(x) ((x) / 180.0 * G_PI) diff --git a/testsuite/meson.build b/testsuite/meson.build index 15c80a07f2..ebcf98d550 100644 --- a/testsuite/meson.build +++ b/testsuite/meson.build @@ -25,7 +25,7 @@ setups = [ { 'backend': 'broadway', 'if': broadway_enabled, }, { 'name': 'wayland_smalltexture', 'backend': 'wayland', 'if': wayland_enabled, - 'env': ['GSK_MAX_TEXTURE_SIZE=1024'], }, + 'env': ['GSK_MAX_TEXTURE_SIZE=1024'] }, { 'backend': 'win32', 'if': os_win32 }, ] @@ -39,6 +39,10 @@ foreach setup : setups exclude += 'gsk-compare-broadway' endif + if name == 'wayland_smalltexture' + exclude += 'wayland_smalltexture_failing' + endif + env = common_env + [ 'GDK_BACKEND=@0@'.format(backend), ] + setup.get('env', [])