Merge branch 'wip/otte/rendernode-opaque' into 'main'

Implement occlusion culling

See merge request GNOME/gtk!7425
This commit is contained in:
Matthias Clasen 2024-07-08 13:53:43 +00:00
commit 8a61b01c06
32 changed files with 1063 additions and 60 deletions

View File

@ -10,7 +10,6 @@
#include "gskgpunodeprocessorprivate.h"
#include "gskgpuopprivate.h"
#include "gskgpurendererprivate.h"
#include "gskgpurenderpassopprivate.h"
#include "gskgpuuploadopprivate.h"
#include "gskdebugprivate.h"
@ -559,29 +558,6 @@ copy_texture (gpointer user_data,
*target = g_object_ref (texture);
}
static void
gsk_gpu_frame_record_rect (GskGpuFrame *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderNode *node,
const graphene_rect_t *viewport)
{
gsk_gpu_render_pass_begin_op (self,
target,
clip,
GSK_RENDER_PASS_PRESENT);
gsk_gpu_node_processor_process (self,
target,
clip,
node,
viewport);
gsk_gpu_render_pass_end_op (self,
target,
GSK_RENDER_PASS_PRESENT);
}
static void
gsk_gpu_frame_record (GskGpuFrame *self,
gint64 timestamp,
@ -604,20 +580,20 @@ gsk_gpu_frame_record (GskGpuFrame *self,
cairo_rectangle_int_t rect;
cairo_region_get_rectangle (clip, i, &rect);
gsk_gpu_frame_record_rect (self, target, &rect, node, viewport);
gsk_gpu_node_processor_process (self, target, &rect, node, viewport);
}
}
else
{
gsk_gpu_frame_record_rect (self,
target,
&(cairo_rectangle_int_t) {
0, 0,
gsk_gpu_image_get_width (target),
gsk_gpu_image_get_height (target)
},
node,
viewport);
gsk_gpu_node_processor_process (self,
target,
&(cairo_rectangle_int_t) {
0, 0,
gsk_gpu_image_get_width (target),
gsk_gpu_image_get_height (target)
},
node,
viewport);
}
if (texture)

View File

@ -123,6 +123,11 @@ struct _GskGpuNodeProcessor
static void gsk_gpu_node_processor_add_node (GskGpuNodeProcessor *self,
GskRenderNode *node);
static gboolean gsk_gpu_node_processor_add_first_node (GskGpuNodeProcessor *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderPassType pass_type,
GskRenderNode *node);
static GskGpuImage * gsk_gpu_get_node_as_image (GskGpuFrame *frame,
const graphene_rect_t *clip_bounds,
const graphene_vec2_t *scale,
@ -346,6 +351,7 @@ gsk_gpu_node_processor_init_draw (GskGpuNodeProcessor *self,
gsk_gpu_render_pass_begin_op (frame,
image,
&area,
&GDK_RGBA_TRANSPARENT,
GSK_RENDER_PASS_OFFSCREEN);
return image;
@ -378,7 +384,25 @@ gsk_gpu_node_processor_process (GskGpuFrame *frame,
clip,
viewport);
gsk_gpu_node_processor_add_node (&self, node);
if (!gsk_gpu_frame_should_optimize (frame, GSK_GPU_OPTIMIZE_OCCLUSION_CULLING) ||
!gsk_gpu_node_processor_add_first_node (&self,
target,
clip,
GSK_RENDER_PASS_PRESENT,
node))
{
gsk_gpu_render_pass_begin_op (frame,
target,
clip,
&GDK_RGBA_TRANSPARENT,
GSK_RENDER_PASS_PRESENT);
gsk_gpu_node_processor_add_node (&self, node);
}
gsk_gpu_render_pass_end_op (frame,
target,
GSK_RENDER_PASS_PRESENT);
gsk_gpu_node_processor_finish (&self);
}
@ -660,6 +684,7 @@ gsk_gpu_copy_image (GskGpuFrame *frame,
gsk_gpu_render_pass_begin_op (other.frame,
copy,
&(cairo_rectangle_int_t) { 0, 0, width, height },
&GDK_RGBA_TRANSPARENT,
GSK_RENDER_PASS_OFFSCREEN);
gsk_gpu_node_processor_sync_globals (&other, 0);
@ -983,6 +1008,20 @@ gsk_gpu_node_processor_add_clip_node (GskGpuNodeProcessor *self,
gsk_clip_node_get_clip (node));
}
static gboolean
gsk_gpu_node_processor_add_first_clip_node (GskGpuNodeProcessor *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderPassType pass_type,
GskRenderNode *node)
{
return gsk_gpu_node_processor_add_first_node (self,
target,
clip,
pass_type,
gsk_clip_node_get_child (node));
}
static void
gsk_gpu_node_processor_add_rounded_clip_node_with_mask (GskGpuNodeProcessor *self,
GskRenderNode *node)
@ -1092,6 +1131,27 @@ gsk_gpu_node_processor_add_rounded_clip_node (GskGpuNodeProcessor *self,
self->pending_globals |= GSK_GPU_GLOBAL_CLIP;
}
static gboolean
gsk_gpu_node_processor_add_first_rounded_clip_node (GskGpuNodeProcessor *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderPassType pass_type,
GskRenderNode *node)
{
GskRoundedRect node_clip;
node_clip = *gsk_rounded_clip_node_get_clip (node);
gsk_rounded_rect_offset (&node_clip, self->offset.x, self->offset.y);
if (!gsk_rounded_rect_contains_rect (&node_clip, &self->clip.rect.bounds))
return FALSE;
return gsk_gpu_node_processor_add_first_node (self,
target,
clip,
pass_type,
gsk_rounded_clip_node_get_child (node));
}
static void
gsk_gpu_node_processor_add_transform_node (GskGpuNodeProcessor *self,
GskRenderNode *node)
@ -1236,6 +1296,81 @@ gsk_gpu_node_processor_add_transform_node (GskGpuNodeProcessor *self,
self->pending_globals |= GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP;
}
static gboolean
gsk_gpu_node_processor_add_first_transform_node (GskGpuNodeProcessor *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderPassType pass_type,
GskRenderNode *node)
{
GskTransform *transform;
float dx, dy, scale_x, scale_y;
GskGpuClip old_clip;
graphene_point_t old_offset;
graphene_vec2_t old_scale;
gboolean result;
transform = gsk_transform_node_get_transform (node);
switch (gsk_transform_get_category (transform))
{
case GSK_TRANSFORM_CATEGORY_IDENTITY:
case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
gsk_transform_to_translate (transform, &dx, &dy);
old_offset = self->offset;
self->offset.x += dx;
self->offset.y += dy;
result = gsk_gpu_node_processor_add_first_node (self,
target,
clip,
pass_type,
gsk_transform_node_get_child (node));
self->offset = old_offset;
return result;
case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
gsk_transform_to_affine (transform, &scale_x, &scale_y, &dx, &dy);
if (scale_x <= 0 || scale_y <= 0)
return FALSE;
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
old_offset = self->offset;
old_scale = self->scale;
gsk_gpu_clip_scale (&self->clip, &old_clip, scale_x, scale_y);
self->offset.x = (self->offset.x + dx) / scale_x;
self->offset.y = (self->offset.y + dy) / scale_y;
graphene_vec2_init (&self->scale, fabs (scale_x), fabs (scale_y));
graphene_vec2_multiply (&self->scale, &old_scale, &self->scale);
self->pending_globals |= GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP;
result = gsk_gpu_node_processor_add_first_node (self,
target,
clip,
pass_type,
gsk_transform_node_get_child (node));
self->offset = old_offset;
self->scale = old_scale;
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
self->pending_globals |= GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP;
return result;
case GSK_TRANSFORM_CATEGORY_2D:
case GSK_TRANSFORM_CATEGORY_UNKNOWN:
case GSK_TRANSFORM_CATEGORY_ANY:
case GSK_TRANSFORM_CATEGORY_3D:
return FALSE;
default:
g_assert_not_reached ();
return FALSE;
}
}
static void
gsk_gpu_node_processor_add_opacity_node (GskGpuNodeProcessor *self,
GskRenderNode *node)
@ -1360,6 +1495,31 @@ gsk_gpu_node_processor_add_color_node (GskGpuNodeProcessor *self,
&GDK_RGBA_INIT_ALPHA (color, self->opacity));
}
static gboolean
gsk_gpu_node_processor_add_first_color_node (GskGpuNodeProcessor *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderPassType pass_type,
GskRenderNode *node)
{
graphene_rect_t clip_bounds;
if (!node->fully_opaque)
return FALSE;
gsk_gpu_node_processor_get_clip_bounds (self, &clip_bounds);
if (!gsk_rect_contains_rect (&node->bounds, &clip_bounds))
return FALSE;
gsk_gpu_render_pass_begin_op (self->frame,
target,
clip,
gsk_color_node_get_color (node),
pass_type);
return TRUE;
}
static void
gsk_gpu_node_processor_add_border_node (GskGpuNodeProcessor *self,
GskRenderNode *node)
@ -2857,16 +3017,50 @@ static void
gsk_gpu_node_processor_add_container_node (GskGpuNodeProcessor *self,
GskRenderNode *node)
{
gsize i;
if (self->opacity < 1.0 && !gsk_container_node_is_disjoint (node))
{
gsk_gpu_node_processor_add_without_opacity (self, node);
return;
}
for (guint i = 0; i < gsk_container_node_get_n_children (node); i++)
for (i = 0; i < gsk_container_node_get_n_children (node); i++)
gsk_gpu_node_processor_add_node (self, gsk_container_node_get_child (node, i));
}
static gboolean
gsk_gpu_node_processor_add_first_container_node (GskGpuNodeProcessor *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderPassType pass_type,
GskRenderNode *node)
{
gsize i, n;
n = gsk_container_node_get_n_children (node);
if (n == 0)
return FALSE;
for (i = n - 1; ; i--)
{
if (gsk_gpu_node_processor_add_first_node (self,
target,
clip,
pass_type,
gsk_container_node_get_child (node, i)))
break;
if (i == 0)
return FALSE;
}
for (i = i + 1; i < n; i++)
gsk_gpu_node_processor_add_node (self, gsk_container_node_get_child (node, i));
return TRUE;
}
static void
gsk_gpu_node_processor_add_debug_node (GskGpuNodeProcessor *self,
GskRenderNode *node)
@ -2898,6 +3092,11 @@ static const struct
GskGpuNodeFeatures features;
void (* process_node) (GskGpuNodeProcessor *self,
GskRenderNode *node);
gboolean (* process_first_node) (GskGpuNodeProcessor *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderPassType pass_type,
GskRenderNode *node);
GskGpuImage * (* get_node_as_image) (GskGpuFrame *self,
const graphene_rect_t *clip_bounds,
const graphene_vec2_t *scale,
@ -2909,23 +3108,27 @@ static const struct
0,
NULL,
NULL,
NULL,
},
[GSK_CONTAINER_NODE] = {
GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_container_node,
gsk_gpu_node_processor_add_first_container_node,
NULL,
},
[GSK_CAIRO_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
NULL,
NULL,
gsk_gpu_get_cairo_node_as_image,
},
[GSK_COLOR_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_color_node,
gsk_gpu_node_processor_add_first_color_node,
NULL,
},
[GSK_LINEAR_GRADIENT_NODE] = {
@ -2933,41 +3136,48 @@ static const struct
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_linear_gradient_node,
NULL,
NULL,
},
[GSK_REPEATING_LINEAR_GRADIENT_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_linear_gradient_node,
NULL,
NULL,
},
[GSK_RADIAL_GRADIENT_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_radial_gradient_node,
NULL,
NULL,
},
[GSK_REPEATING_RADIAL_GRADIENT_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_radial_gradient_node,
NULL,
NULL,
},
[GSK_CONIC_GRADIENT_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_conic_gradient_node,
NULL,
NULL,
},
[GSK_BORDER_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_border_node,
NULL,
NULL,
},
[GSK_TEXTURE_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_texture_node,
NULL,
gsk_gpu_get_texture_node_as_image,
},
[GSK_INSET_SHADOW_NODE] = {
@ -2975,17 +3185,20 @@ static const struct
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_inset_shadow_node,
NULL,
NULL,
},
[GSK_OUTSET_SHADOW_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_outset_shadow_node,
NULL,
NULL,
},
[GSK_TRANSFORM_NODE] = {
GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_BLEND,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_transform_node,
gsk_gpu_node_processor_add_first_transform_node,
NULL,
},
[GSK_OPACITY_NODE] = {
@ -2993,29 +3206,34 @@ static const struct
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_opacity_node,
NULL,
NULL,
},
[GSK_COLOR_MATRIX_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_color_matrix_node,
NULL,
NULL,
},
[GSK_REPEAT_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_repeat_node,
NULL,
NULL,
},
[GSK_CLIP_NODE] = {
GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_BLEND,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_clip_node,
gsk_gpu_node_processor_add_first_clip_node,
NULL,
},
[GSK_ROUNDED_CLIP_NODE] = {
GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_BLEND,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_rounded_clip_node,
gsk_gpu_node_processor_add_first_rounded_clip_node,
NULL,
},
[GSK_SHADOW_NODE] = {
@ -3023,35 +3241,41 @@ static const struct
0,
gsk_gpu_node_processor_add_shadow_node,
NULL,
NULL,
},
[GSK_BLEND_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_blend_node,
NULL,
NULL,
},
[GSK_CROSS_FADE_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_cross_fade_node,
NULL,
NULL,
},
[GSK_TEXT_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_glyph_node,
NULL,
NULL,
},
[GSK_BLUR_NODE] = {
0,
0,
gsk_gpu_node_processor_add_blur_node,
NULL,
NULL,
},
[GSK_DEBUG_NODE] = {
GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_BLEND,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_debug_node,
NULL,
gsk_gpu_get_debug_node_as_image,
},
[GSK_GL_SHADER_NODE] = {
@ -3059,35 +3283,41 @@ static const struct
0,
NULL,
NULL,
NULL,
},
[GSK_TEXTURE_SCALE_NODE] = {
0,
0,
gsk_gpu_node_processor_add_texture_scale_node,
NULL,
NULL,
},
[GSK_MASK_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_mask_node,
NULL,
NULL,
},
[GSK_FILL_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_fill_node,
NULL,
NULL,
},
[GSK_STROKE_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_stroke_node,
NULL,
NULL,
},
[GSK_SUBSURFACE_NODE] = {
GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_BLEND,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_subsurface_node,
NULL,
gsk_gpu_get_subsurface_node_as_image,
},
};
@ -3136,6 +3366,65 @@ gsk_gpu_node_processor_add_node (GskGpuNodeProcessor *self,
}
}
static gboolean
clip_covered_by_rect (const GskGpuClip *self,
const graphene_point_t *offset,
const graphene_rect_t *rect)
{
graphene_rect_t r = *rect;
r.origin.x += offset->x;
r.origin.y += offset->y;
return gsk_rect_contains_rect (&r, &self->rect.bounds);
}
static gboolean
gsk_gpu_node_processor_add_first_node (GskGpuNodeProcessor *self,
GskGpuImage *target,
const cairo_rectangle_int_t *clip,
GskRenderPassType pass_type,
GskRenderNode *node)
{
GskRenderNodeType node_type;
graphene_rect_t opaque, clip_bounds;
/* This catches the corner cases of empty nodes, so after this check
* there's quaranteed to be at least 1 pixel that needs to be drawn
*/
if (node->bounds.size.width == 0 || node->bounds.size.height == 0 ||
!clip_covered_by_rect (&self->clip, &self->offset, &node->bounds))
return FALSE;
node_type = gsk_render_node_get_node_type (node);
if (node_type >= G_N_ELEMENTS (nodes_vtable))
{
g_critical ("unknown node type %u for %s", node_type, g_type_name_from_instance ((GTypeInstance *) node));
return FALSE;
}
if (nodes_vtable[node_type].process_first_node)
return nodes_vtable[node_type].process_first_node (self, target, clip, pass_type, node);
/* fallback starts here */
if (!gsk_render_node_get_opaque_rect (node, &opaque))
return FALSE;
gsk_gpu_node_processor_get_clip_bounds (self, &clip_bounds);
if (!gsk_rect_contains_rect (&clip_bounds, &opaque))
return FALSE;
gsk_gpu_render_pass_begin_op (self->frame,
target,
clip,
&GDK_RGBA_TRANSPARENT,
pass_type);
gsk_gpu_node_processor_add_node (self, node);
return TRUE;
}
/*
* gsk_gpu_get_node_as_image:
* @frame: frame to render in

View File

@ -24,12 +24,13 @@
#define GSK_GPU_MAX_FRAMES 4
static const GdkDebugKey gsk_gpu_optimization_keys[] = {
{ "clear", GSK_GPU_OPTIMIZE_CLEAR, "Use shaders instead of vkCmdClearAttachment()/glClear()" },
{ "merge", GSK_GPU_OPTIMIZE_MERGE, "Use one vkCmdDraw()/glDrawArrays() per operation" },
{ "blit", GSK_GPU_OPTIMIZE_BLIT, "Use shaders instead of vkCmdBlit()/glBlitFramebuffer()" },
{ "gradients", GSK_GPU_OPTIMIZE_GRADIENTS, "Don't supersample gradients" },
{ "mipmap", GSK_GPU_OPTIMIZE_MIPMAP, "Avoid creating mipmaps" },
{ "to-image", GSK_GPU_OPTIMIZE_TO_IMAGE, "Don't fast-path creation of images for nodes" },
{ "clear", GSK_GPU_OPTIMIZE_CLEAR, "Use shaders instead of vkCmdClearAttachment()/glClear()" },
{ "merge", GSK_GPU_OPTIMIZE_MERGE, "Use one vkCmdDraw()/glDrawArrays() per operation" },
{ "blit", GSK_GPU_OPTIMIZE_BLIT, "Use shaders instead of vkCmdBlit()/glBlitFramebuffer()" },
{ "gradients", GSK_GPU_OPTIMIZE_GRADIENTS, "Don't supersample gradients" },
{ "mipmap", GSK_GPU_OPTIMIZE_MIPMAP, "Avoid creating mipmaps" },
{ "to-image", GSK_GPU_OPTIMIZE_TO_IMAGE, "Don't fast-path creation of images for nodes" },
{ "occlusion", GSK_GPU_OPTIMIZE_OCCLUSION_CULLING, "Disable occlusion culling via opaque node tracking" },
};
typedef struct _GskGpuRendererPrivate GskGpuRendererPrivate;

View File

@ -22,6 +22,7 @@ struct _GskGpuRenderPassOp
GskGpuImage *target;
cairo_rectangle_int_t area;
float background[4];
GskRenderPassType pass_type;
};
@ -43,6 +44,9 @@ gsk_gpu_render_pass_op_print (GskGpuOp *op,
gsk_gpu_print_op (string, indent, "begin-render-pass");
gsk_gpu_print_image (string, self->target);
gsk_gpu_print_int_rect (string, &self->area);
if (self->background[3] > 0)
gsk_gpu_print_rgba (string, self->background);
gsk_gpu_print_newline (string);
}
@ -137,7 +141,16 @@ gsk_gpu_render_pass_op_vk_command (GskGpuOp *op,
},
.clearValueCount = 1,
.pClearValues = (VkClearValue [1]) {
{ .color = { .float32 = { 0.f, 0.f, 0.f, 0.f } } }
{
.color = {
.float32 = {
self->background[0],
self->background[1],
self->background[2],
self->background[3]
}
}
}
}
},
VK_SUBPASS_CONTENTS_INLINE);
@ -179,7 +192,7 @@ gsk_gpu_render_pass_op_gl_command (GskGpuOp *op,
glScissor (self->area.x, state->flip_y - self->area.y - self->area.height, self->area.width, self->area.height);
else
glScissor (self->area.x, self->area.y, self->area.width, self->area.height);
glClearColor (0, 0, 0, 0);
glClearColor (self->background[0], self->background[1], self->background[2], self->background[3]);
glClear (GL_COLOR_BUFFER_BIT);
op = op->next;
@ -311,6 +324,7 @@ void
gsk_gpu_render_pass_begin_op (GskGpuFrame *frame,
GskGpuImage *image,
const cairo_rectangle_int_t *area,
const GdkRGBA *background,
GskRenderPassType pass_type)
{
GskGpuRenderPassOp *self;
@ -319,6 +333,7 @@ gsk_gpu_render_pass_begin_op (GskGpuFrame *frame,
self->target = g_object_ref (image);
self->area = *area;
gsk_gpu_rgba_to_float (background, self->background);
self->pass_type = pass_type;
}

View File

@ -18,6 +18,7 @@ typedef enum
void gsk_gpu_render_pass_begin_op (GskGpuFrame *frame,
GskGpuImage *image,
const cairo_rectangle_int_t *area,
const GdkRGBA *background,
GskRenderPassType pass_type);
void gsk_gpu_render_pass_end_op (GskGpuFrame *frame,
GskGpuImage *image,

View File

@ -119,5 +119,6 @@ typedef enum {
GSK_GPU_OPTIMIZE_GRADIENTS = 1 << 3,
GSK_GPU_OPTIMIZE_MIPMAP = 1 << 4,
GSK_GPU_OPTIMIZE_TO_IMAGE = 1 << 5,
GSK_GPU_OPTIMIZE_OCCLUSION_CULLING = 1 << 6,
} GskGpuOptimizations;

View File

@ -85,6 +85,81 @@ gsk_rect_intersection (const graphene_rect_t *r1,
}
}
/**
* gsk_rect_coverage:
* @r1: a valid rectangle
* @r2: another valid rectangle
* @res: The result, may be one of r1/r2
*
* Computes the largest rectangle that is fully covered by
* r1 and r2.
*
* Note that this is different from a union, which is the smallest
* rectangle that covers the rectangles.
*
* The use case for this function is joining opaque rectangles.
**/
static inline void
gsk_rect_coverage (const graphene_rect_t *r1,
const graphene_rect_t *r2,
graphene_rect_t *res)
{
float x1min, y1min, x2min, y2min;
float x1max, y1max, x2max, y2max;
float size, size2;
graphene_rect_t r;
/* Assumes both rects are already normalized, as they usually are */
size = r1->size.width * r1->size.height;
size2 = r2->size.width * r2->size.height;
if (size >= size2)
{
r = *r1;
}
else
{
r = *r2;
size = size2;
}
x1min = MIN (r1->origin.x, r2->origin.x);
y1min = MIN (r1->origin.y, r2->origin.y);
x1max = MAX (r1->origin.x, r2->origin.x);
y1max = MAX (r1->origin.y, r2->origin.y);
x2min = MIN (r1->origin.x + r1->size.width, r2->origin.x + r2->size.width);
y2min = MIN (r1->origin.y + r1->size.height, r2->origin.y + r2->size.height);
x2max = MAX (r1->origin.x + r1->size.width, r2->origin.x + r2->size.width);
y2max = MAX (r1->origin.y + r1->size.height, r2->origin.y + r2->size.height);
if (x2min >= x1max)
{
float w, h;
w = x2min - x1max;
h = y2max - y1min;
size2 = w * h;
if (size2 > size)
{
r = GRAPHENE_RECT_INIT (x1max, y1min, w, h);
size = size2;
}
}
if (y2min >= y1max)
{
float w, h;
w = x2max - x1min;
h = y2min - y1max;
size2 = w * h;
if (size2 > size)
{
r = GRAPHENE_RECT_INIT (x1min, y1max, w, h);
size = size2;
}
}
*res = r;
}
static inline gboolean G_GNUC_PURE
gsk_rect_is_empty (const graphene_rect_t *rect)
{

View File

@ -160,6 +160,13 @@ gsk_render_node_real_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, data);
}
static gboolean
gsk_render_node_real_get_opaque_rect (GskRenderNode *node,
graphene_rect_t *out_opaque)
{
return FALSE;
}
static void
gsk_render_node_class_init (GskRenderNodeClass *klass)
{
@ -167,6 +174,7 @@ gsk_render_node_class_init (GskRenderNodeClass *klass)
klass->finalize = gsk_render_node_finalize;
klass->can_diff = gsk_render_node_real_can_diff;
klass->diff = gsk_render_node_real_diff;
klass->get_opaque_rect = gsk_render_node_real_get_opaque_rect;
}
static void
@ -559,6 +567,41 @@ gsk_render_node_diff (GskRenderNode *node1,
}
}
/**
* gsk_render_node_get_opaque_rect:
* @self: a `GskRenderNode`
* @out_opaque: (out):
*
* Gets an opaque rectangle inside the node that GTK can determine to
* be fully opaque.
*
* There is no guarantee that this is indeed the largest opaque rectangle or
* that regions outside the rectangle are not opaque. This function is a best
* effort with that goal.
*
* The rectangle will be fully contained in the bounds of the node.
*
* Returns: %TRUE if part or all of the rendernode is opaque, %FALSE if no
* opaque region could be found.
*
* Since: 4.16
**/
gboolean
gsk_render_node_get_opaque_rect (GskRenderNode *self,
graphene_rect_t *out_opaque)
{
g_return_val_if_fail (GSK_IS_RENDER_NODE (self), FALSE);
g_return_val_if_fail (out_opaque != NULL, FALSE);
if (self->fully_opaque)
{
*out_opaque = self->bounds;
return TRUE;
}
return GSK_RENDER_NODE_GET_CLASS (self)->get_opaque_rect (self, out_opaque);
}
/**
* gsk_render_node_write_to_file:
* @node: a `GskRenderNode`

View File

@ -122,6 +122,10 @@ GDK_AVAILABLE_IN_ALL
void gsk_render_node_get_bounds (GskRenderNode *node,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_4_16
gboolean gsk_render_node_get_opaque_rect (GskRenderNode *self,
graphene_rect_t *out_opaque);
GDK_AVAILABLE_IN_ALL
void gsk_render_node_draw (GskRenderNode *node,
cairo_t *cr);

View File

@ -58,6 +58,21 @@
*/
#define MAX_RECTS_IN_DIFF 30
static gboolean
gsk_color_stops_are_opaque (const GskColorStop *stops,
gsize n_stops)
{
gsize i;
for (i = 0; i < n_stops; i++)
{
if (!gdk_rgba_is_opaque (&stops[i].color))
return FALSE;
}
return TRUE;
}
static inline void
gsk_cairo_rectangle (cairo_t *cr,
const graphene_rect_t *rect)
@ -233,6 +248,7 @@ gsk_color_node_new (const GdkRGBA *rgba,
self = gsk_render_node_alloc (GSK_COLOR_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
node->fully_opaque = gdk_rgba_is_opaque (rgba);
self->color = *rgba;
gsk_rect_init_from_rect (&node->bounds, bounds);
@ -421,6 +437,7 @@ gsk_linear_gradient_node_new (const graphene_rect_t *bounds,
self = gsk_render_node_alloc (GSK_LINEAR_GRADIENT_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
gsk_rect_init_from_rect (&node->bounds, bounds);
gsk_rect_normalize (&node->bounds);
@ -475,6 +492,7 @@ gsk_repeating_linear_gradient_node_new (const graphene_rect_t *bounds,
self = gsk_render_node_alloc (GSK_REPEATING_LINEAR_GRADIENT_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
gsk_rect_init_from_rect (&node->bounds, bounds);
gsk_rect_normalize (&node->bounds);
@ -768,6 +786,7 @@ gsk_radial_gradient_node_new (const graphene_rect_t *bounds,
self = gsk_render_node_alloc (GSK_RADIAL_GRADIENT_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
gsk_rect_init_from_rect (&node->bounds, bounds);
gsk_rect_normalize (&node->bounds);
@ -838,6 +857,7 @@ gsk_repeating_radial_gradient_node_new (const graphene_rect_t *bounds,
self = gsk_render_node_alloc (GSK_REPEATING_RADIAL_GRADIENT_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
gsk_rect_init_from_rect (&node->bounds, bounds);
gsk_rect_normalize (&node->bounds);
@ -1232,6 +1252,7 @@ gsk_conic_gradient_node_new (const graphene_rect_t *bounds,
self = gsk_render_node_alloc (GSK_CONIC_GRADIENT_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
node->fully_opaque = gsk_color_stops_are_opaque (color_stops, n_color_stops);
gsk_rect_init_from_rect (&node->bounds, bounds);
gsk_rect_normalize (&node->bounds);
@ -1877,6 +1898,7 @@ gsk_texture_node_new (GdkTexture *texture,
self = gsk_render_node_alloc (GSK_TEXTURE_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
node->fully_opaque = gdk_memory_format_alpha (gdk_texture_get_format (texture)) == GDK_MEMORY_ALPHA_OPAQUE;
self->texture = g_object_ref (texture);
gsk_rect_init_from_rect (&node->bounds, bounds);
@ -2095,6 +2117,9 @@ gsk_texture_scale_node_new (GdkTexture *texture,
self = gsk_render_node_alloc (GSK_TEXTURE_SCALE_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = FALSE;
node->fully_opaque = gdk_memory_format_alpha (gdk_texture_get_format (texture)) == GDK_MEMORY_ALPHA_OPAQUE &&
bounds->size.width == floor (bounds->size.width) &&
bounds->size.height == floor (bounds->size.height);
self->texture = g_object_ref (texture);
gsk_rect_init_from_rect (&node->bounds, bounds);
@ -3158,6 +3183,7 @@ struct _GskContainerNode
GskRenderNode render_node;
gboolean disjoint;
graphene_rect_t opaque; /* Can be 0 0 0 0 to mean no opacity */
guint n_children;
GskRenderNode **children;
};
@ -3286,6 +3312,19 @@ gsk_container_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, data);
}
static gboolean
gsk_container_node_get_opaque_rect (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskContainerNode *self = (GskContainerNode *) node;
if (self->opaque.size.width <= 0 && self->opaque.size.height <= 0)
return FALSE;
*opaque = self->opaque;
return TRUE;
}
static void
gsk_container_node_class_init (gpointer g_class,
gpointer class_data)
@ -3297,6 +3336,7 @@ gsk_container_node_class_init (gpointer g_class,
node_class->finalize = gsk_container_node_finalize;
node_class->draw = gsk_container_node_draw;
node_class->diff = gsk_container_node_diff;
node_class->get_opaque_rect = gsk_container_node_get_opaque_rect;
}
/**
@ -3329,25 +3369,36 @@ gsk_container_node_new (GskRenderNode **children,
}
else
{
graphene_rect_t bounds;
graphene_rect_t child_opaque;
gboolean have_opaque;
self->children = g_malloc_n (n_children, sizeof (GskRenderNode *));
self->children[0] = gsk_render_node_ref (children[0]);
node->offscreen_for_opacity = children[0]->offscreen_for_opacity;
node->preferred_depth = children[0]->preferred_depth;
gsk_rect_init_from_rect (&bounds, &(children[0]->bounds));
gsk_rect_init_from_rect (&node->bounds, &(children[0]->bounds));
have_opaque = gsk_render_node_get_opaque_rect (self->children[0], &self->opaque);
for (guint i = 1; i < n_children; i++)
{
self->children[i] = gsk_render_node_ref (children[i]);
self->disjoint = self->disjoint && !gsk_rect_intersects (&bounds, &(children[i]->bounds));
graphene_rect_union (&bounds, &(children[i]->bounds), &bounds);
self->disjoint = self->disjoint && !gsk_rect_intersects (&node->bounds, &(children[i]->bounds));
graphene_rect_union (&node->bounds, &(children[i]->bounds), &node->bounds);
node->preferred_depth = gdk_memory_depth_merge (node->preferred_depth, children[i]->preferred_depth);
node->offscreen_for_opacity = node->offscreen_for_opacity || children[i]->offscreen_for_opacity;
if (gsk_render_node_get_opaque_rect (self->children[i], &child_opaque))
{
if (have_opaque)
gsk_rect_coverage (&self->opaque, &child_opaque, &self->opaque);
else
{
self->opaque = child_opaque;
have_opaque = TRUE;
}
}
}
gsk_rect_init_from_rect (&node->bounds, &bounds);
node->offscreen_for_opacity = node->offscreen_for_opacity || !self->disjoint;
}
@ -3567,6 +3618,25 @@ gsk_transform_node_diff (GskRenderNode *node1,
}
}
static gboolean
gsk_transform_node_get_opaque_rect (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskTransformNode *self = (GskTransformNode *) node;
graphene_rect_t child_opaque;
if (gsk_transform_get_category (self->transform) < GSK_TRANSFORM_CATEGORY_2D_AFFINE)
return FALSE;
if (!gsk_render_node_get_opaque_rect (self->child, &child_opaque))
return FALSE;
gsk_transform_transform_bounds (self->transform,
&child_opaque,
opaque);
return TRUE;
}
static void
gsk_transform_node_class_init (gpointer g_class,
gpointer class_data)
@ -3579,6 +3649,7 @@ gsk_transform_node_class_init (gpointer g_class,
node_class->draw = gsk_transform_node_draw;
node_class->can_diff = gsk_transform_node_can_diff;
node_class->diff = gsk_transform_node_diff;
node_class->get_opaque_rect = gsk_transform_node_get_opaque_rect;
}
/**
@ -3597,18 +3668,22 @@ gsk_transform_node_new (GskRenderNode *child,
{
GskTransformNode *self;
GskRenderNode *node;
GskTransformCategory category;
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
g_return_val_if_fail (transform != NULL, NULL);
category = gsk_transform_get_category (transform);
self = gsk_render_node_alloc (GSK_TRANSFORM_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = child->offscreen_for_opacity;
node->fully_opaque = child->fully_opaque && category >= GSK_TRANSFORM_CATEGORY_2D_AFFINE;
self->child = gsk_render_node_ref (child);
self->transform = gsk_transform_ref (transform);
if (gsk_transform_get_category (transform) >= GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)
if (category >= GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)
gsk_transform_to_translate (transform, &self->dx, &self->dy);
else
self->dx = self->dy = 0;
@ -4303,6 +4378,7 @@ gsk_repeat_node_new (const graphene_rect_t *bounds,
}
node->preferred_depth = gsk_render_node_get_preferred_depth (child);
node->fully_opaque = child->fully_opaque && gsk_rect_contains_rect (&child->bounds, &self->child_bounds);
return node;
}
@ -4407,10 +4483,23 @@ gsk_clip_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, data);
}
}
static gboolean
gsk_clip_node_get_opaque_rect (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskClipNode *self = (GskClipNode *) node;
graphene_rect_t child_opaque;
if (!gsk_render_node_get_opaque_rect (self->child, &child_opaque))
return FALSE;
return graphene_rect_intersection (&self->clip, &child_opaque, opaque);
}
static void
gsk_clip_node_class_init (gpointer g_class,
gpointer class_data)
gpointer class_data)
{
GskRenderNodeClass *node_class = g_class;
@ -4419,6 +4508,7 @@ gsk_clip_node_class_init (gpointer g_class,
node_class->finalize = gsk_clip_node_finalize;
node_class->draw = gsk_clip_node_draw;
node_class->diff = gsk_clip_node_diff;
node_class->get_opaque_rect = gsk_clip_node_get_opaque_rect;
}
/**
@ -4444,6 +4534,8 @@ gsk_clip_node_new (GskRenderNode *child,
self = gsk_render_node_alloc (GSK_CLIP_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = child->offscreen_for_opacity;
/* because of the intersection when computing bounds */
node->fully_opaque = child->fully_opaque;
self->child = gsk_render_node_ref (child);
gsk_rect_init_from_rect (&self->clip, clip);
@ -4557,6 +4649,39 @@ gsk_rounded_clip_node_diff (GskRenderNode *node1,
}
}
static gboolean
gsk_rounded_clip_node_get_opaque_rect (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskRoundedClipNode *self = (GskRoundedClipNode *) node;
graphene_rect_t child_opaque, wide_opaque, high_opaque;
double start, end;
if (!gsk_render_node_get_opaque_rect (self->child, &child_opaque))
return FALSE;
wide_opaque = self->clip.bounds;
start = MAX(self->clip.corner[GSK_CORNER_TOP_LEFT].height, self->clip.corner[GSK_CORNER_TOP_RIGHT].height);
end = MAX(self->clip.corner[GSK_CORNER_BOTTOM_LEFT].height, self->clip.corner[GSK_CORNER_BOTTOM_RIGHT].height);
wide_opaque.size.height -= MIN (wide_opaque.size.height, start + end);
wide_opaque.origin.y += start;
graphene_rect_intersection (&wide_opaque, &child_opaque, &wide_opaque);
high_opaque = self->clip.bounds;
start = MAX(self->clip.corner[GSK_CORNER_TOP_LEFT].width, self->clip.corner[GSK_CORNER_BOTTOM_LEFT].width);
end = MAX(self->clip.corner[GSK_CORNER_TOP_RIGHT].width, self->clip.corner[GSK_CORNER_BOTTOM_RIGHT].width);
high_opaque.size.width -= MIN (high_opaque.size.width, start + end);
high_opaque.origin.x += start;
graphene_rect_intersection (&high_opaque, &child_opaque, &high_opaque);
if (wide_opaque.size.width * wide_opaque.size.height > high_opaque.size.width * high_opaque.size.height)
*opaque = wide_opaque;
else
*opaque = high_opaque;
return TRUE;
}
static void
gsk_rounded_clip_node_class_init (gpointer g_class,
gpointer class_data)
@ -4568,6 +4693,7 @@ gsk_rounded_clip_node_class_init (gpointer g_class,
node_class->finalize = gsk_rounded_clip_node_finalize;
node_class->draw = gsk_rounded_clip_node_draw;
node_class->diff = gsk_rounded_clip_node_diff;
node_class->get_opaque_rect = gsk_rounded_clip_node_get_opaque_rect;
}
/**
@ -5589,6 +5715,20 @@ gsk_cross_fade_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, data);
}
static gboolean
gsk_cross_fade_node_get_opaque_rect (GskRenderNode *node,
graphene_rect_t *opaque)
{
GskCrossFadeNode *self = (GskCrossFadeNode *) node;
graphene_rect_t start_opaque, end_opaque;
if (!gsk_render_node_get_opaque_rect (self->start, &start_opaque) ||
!gsk_render_node_get_opaque_rect (self->end, &end_opaque))
return FALSE;
return graphene_rect_intersection (&start_opaque, &end_opaque, opaque);
}
static void
gsk_cross_fade_node_class_init (gpointer g_class,
gpointer class_data)
@ -5600,6 +5740,7 @@ gsk_cross_fade_node_class_init (gpointer g_class,
node_class->finalize = gsk_cross_fade_node_finalize;
node_class->draw = gsk_cross_fade_node_draw;
node_class->diff = gsk_cross_fade_node_diff;
node_class->get_opaque_rect = gsk_cross_fade_node_get_opaque_rect;
}
/**
@ -5627,6 +5768,8 @@ gsk_cross_fade_node_new (GskRenderNode *start,
self = gsk_render_node_alloc (GSK_CROSS_FADE_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = TRUE;
node->fully_opaque = start->fully_opaque && end->fully_opaque &&
gsk_rect_equal (&start->bounds, &end->bounds);
self->start = gsk_render_node_ref (start);
self->end = gsk_render_node_ref (end);
@ -6633,6 +6776,15 @@ gsk_debug_node_diff (GskRenderNode *node1,
gsk_render_node_diff (self1->child, self2->child, data);
}
static gboolean
gsk_debug_node_get_opaque_rect (GskRenderNode *node,
graphene_rect_t *out_opaque)
{
GskDebugNode *self = (GskDebugNode *) node;
return gsk_render_node_get_opaque_rect (self->child, out_opaque);
}
static void
gsk_debug_node_class_init (gpointer g_class,
gpointer class_data)
@ -6645,6 +6797,7 @@ gsk_debug_node_class_init (gpointer g_class,
node_class->draw = gsk_debug_node_draw;
node_class->can_diff = gsk_debug_node_can_diff;
node_class->diff = gsk_debug_node_diff;
node_class->get_opaque_rect = gsk_debug_node_get_opaque_rect;
}
/**
@ -6671,6 +6824,7 @@ gsk_debug_node_new (GskRenderNode *child,
self = gsk_render_node_alloc (GSK_DEBUG_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = child->offscreen_for_opacity;
node->fully_opaque = child->fully_opaque;
self->child = gsk_render_node_ref (child);
self->message = message;
@ -7032,6 +7186,15 @@ gsk_subsurface_node_diff (GskRenderNode *node1,
}
}
static gboolean
gsk_subsurface_node_get_opaque_rect (GskRenderNode *node,
graphene_rect_t *out_opaque)
{
GskSubsurfaceNode *self = (GskSubsurfaceNode *) node;
return gsk_render_node_get_opaque_rect (self->child, out_opaque);
}
static void
gsk_subsurface_node_class_init (gpointer g_class,
gpointer class_data)
@ -7044,6 +7207,7 @@ gsk_subsurface_node_class_init (gpointer g_class,
node_class->draw = gsk_subsurface_node_draw;
node_class->can_diff = gsk_subsurface_node_can_diff;
node_class->diff = gsk_subsurface_node_diff;
node_class->get_opaque_rect = gsk_subsurface_node_get_opaque_rect;
}
/**
@ -7074,6 +7238,7 @@ gsk_subsurface_node_new (GskRenderNode *child,
self = gsk_render_node_alloc (GSK_SUBSURFACE_NODE);
node = (GskRenderNode *) self;
node->offscreen_for_opacity = child->offscreen_for_opacity;
node->fully_opaque = child->fully_opaque;
self->child = gsk_render_node_ref (child);
if (subsurface)

View File

@ -34,6 +34,7 @@ struct _GskRenderNode
guint preferred_depth : GDK_MEMORY_DEPTH_BITS;
guint offscreen_for_opacity : 1;
guint fully_opaque : 1;
};
typedef struct
@ -48,14 +49,16 @@ struct _GskRenderNodeClass
GskRenderNodeType node_type;
void (* finalize) (GskRenderNode *node);
void (* draw) (GskRenderNode *node,
cairo_t *cr);
gboolean (* can_diff) (const GskRenderNode *node1,
const GskRenderNode *node2);
void (* diff) (GskRenderNode *node1,
GskRenderNode *node2,
GskDiffData *data);
void (* finalize) (GskRenderNode *node);
void (* draw) (GskRenderNode *node,
cairo_t *cr);
gboolean (* can_diff) (const GskRenderNode *node1,
const GskRenderNode *node2);
void (* diff) (GskRenderNode *node1,
GskRenderNode *node2,
GskDiffData *data);
gboolean (* get_opaque_rect) (GskRenderNode *node,
graphene_rect_t *out_opaque);
};
void gsk_render_node_init_types (void);

View File

@ -511,6 +511,7 @@ tests = [
[ 'normalize', [ 'normalize.c', '../reftests/reftest-compare.c' ] ],
[ 'transform' ],
[ 'shader' ],
[ 'opaque' ],
[ 'path', [ 'path-utils.c' ], [ 'flaky'] ],
[ 'path-special-cases' ],
[ 'scaling' ],

281
testsuite/gsk/opaque.c Normal file
View File

@ -0,0 +1,281 @@
/*
* Copyright (C) 2024 Red Hat Inc.
*
* Author:
* Benjamin Otte <otte@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gtk/gtk.h>
#include <errno.h>
static gboolean using_tap;
static gboolean
parse_float (const char *input,
float *out)
{
char *s;
float f;
f = g_ascii_strtod (input, &s);
if (errno == ERANGE || s == input || *s != 0 ||
isinf (f) || isnan (f))
return FALSE;
*out = f;
return TRUE;
}
static gboolean
parse_rect_from_filename (const char *filename,
graphene_rect_t *out_rect)
{
char **parts;
gsize n;
gboolean result;
parts = g_strsplit (filename, "-", -1);
n = g_strv_length (parts);
if (g_str_has_suffix (parts[n-1], ".node"))
parts[n-1][strlen(parts[n-1])-5] = 0;
result = n > 4 &&
parse_float (parts[n-4], &out_rect->origin.x) &&
parse_float (parts[n-3], &out_rect->origin.y) &&
parse_float (parts[n-2], &out_rect->size.width) &&
parse_float (parts[n-1], &out_rect->size.height);
g_strfreev (parts);
return result;
}
static void
deserialize_error_func (const GskParseLocation *start,
const GskParseLocation *end,
const GError *error,
gpointer user_data)
{
GString *string = g_string_new (user_data);
g_string_append_printf (string, ":%zu:%zu",
start->lines + 1, start->line_chars + 1);
if (start->lines != end->lines || start->line_chars != end->line_chars)
{
g_string_append (string, "-");
if (start->lines != end->lines)
g_string_append_printf (string, "%zu:", end->lines + 1);
g_string_append_printf (string, "%zu", end->line_chars + 1);
}
g_test_message ("%s", string->str);
g_string_free (string, TRUE);
g_test_fail ();
}
static void
test_opaqueness (GFile *file)
{
GskRenderNode *node;
char *node_file;
GBytes *bytes;
GError *error = NULL;
graphene_rect_t opaque, expected;
gboolean is_opaque;
bytes = g_file_load_bytes (file, NULL, NULL, &error);
if (error)
{
g_test_message ("Failed to load file: %s", error->message);
g_clear_error (&error);
g_test_fail ();
return;
}
g_assert_nonnull (bytes);
node = gsk_render_node_deserialize (bytes, deserialize_error_func, file);
g_bytes_unref (bytes);
is_opaque = gsk_render_node_get_opaque_rect (node, &opaque);
node_file = g_file_get_path (file);
if (parse_rect_from_filename (node_file, &expected))
{
if (is_opaque)
{
if (!graphene_rect_equal (&opaque, &expected))
{
g_test_message ("Should be %g %g %g %g but is %g %g %g %g",
expected.origin.x, expected.origin.y,
expected.size.width, expected.size.height,
opaque.origin.x, opaque.origin.y,
opaque.size.width, opaque.size.height);
g_test_fail ();
}
}
else
{
g_test_message ("Should be %g %g %g %g but is not opaque",
expected.origin.x, expected.origin.y,
expected.size.width, expected.size.height);
g_test_fail ();
}
}
else
{
if (is_opaque)
{
g_test_message ("Should not be opaque, but is %g %g %g %g",
opaque.origin.x, opaque.origin.y,
opaque.size.width, opaque.size.height);
g_test_fail ();
}
}
gsk_render_node_unref (node);
g_free (node_file);
}
static int
compare_files (gconstpointer a, gconstpointer b)
{
GFile *file1 = G_FILE (a);
GFile *file2 = G_FILE (b);
char *path1, *path2;
int result;
path1 = g_file_get_path (file1);
path2 = g_file_get_path (file2);
result = strcmp (path1, path2);
g_free (path1);
g_free (path2);
return result;
}
static void
add_test_for_file (GFile *file)
{
GFileEnumerator *enumerator;
GFileInfo *info;
GList *files;
GError *error = NULL;
if (g_file_query_file_type (file, 0, NULL) != G_FILE_TYPE_DIRECTORY)
{
g_test_add_vtable (g_file_peek_path (file),
0,
g_object_ref (file),
NULL,
(GTestFixtureFunc) test_opaqueness,
(GTestFixtureFunc) g_object_unref);
return;
}
enumerator = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error);
g_assert_no_error (error);
files = NULL;
while ((info = g_file_enumerator_next_file (enumerator, NULL, &error)))
{
const char *filename;
filename = g_file_info_get_name (info);
if (!g_str_has_suffix (filename, ".node"))
{
g_object_unref (info);
continue;
}
files = g_list_prepend (files, g_file_get_child (file, filename));
g_object_unref (info);
}
g_assert_no_error (error);
g_object_unref (enumerator);
files = g_list_sort (files, compare_files);
g_list_foreach (files, (GFunc) add_test_for_file, NULL);
g_list_free_full (files, g_object_unref);
}
static gboolean
parse_command_line (int *argc, char ***argv)
{
int i;
for (i = 0; i < *argc; i++)
{
if (strcmp ((*argv)[i], "--tap") == 0)
using_tap = TRUE;
}
gtk_test_init (argc, argv);
return TRUE;
}
int
main (int argc, char **argv)
{
int result;
if (!parse_command_line (&argc, &argv))
return 1;
if (argc < 2)
{
char *dirname;
GFile *dir;
dirname = g_build_filename (g_test_get_dir (G_TEST_DIST), "opaque", NULL);
dir = g_file_new_for_path (dirname);
g_free (dirname);
add_test_for_file (dir);
g_object_unref (dir);
}
else
{
guint i;
for (i = 1; i < argc; i++)
{
GFile *file = g_file_new_for_commandline_arg (argv[i]);
add_test_for_file (file);
g_object_unref (file);
}
}
result = g_test_run ();
if (using_tap)
return 0;
return result;
}

View File

@ -0,0 +1 @@
color { }

View File

@ -0,0 +1 @@
conic-gradient { }

View File

@ -0,0 +1 @@
linear-gradient { }

View File

@ -0,0 +1 @@
radial-gradient { }

View File

@ -0,0 +1 @@
texture-scale { }

View File

@ -0,0 +1 @@
texture { }

View File

View File

@ -0,0 +1,26 @@
/* the covered node */
color {
bounds: 30 30 40 40;
color: black;
}
container {
color {
bounds: 0 0 50 50;
color: lime;
}
color {
bounds: 50 0 50 50;
color: red;
}
}
container {
color {
bounds: 0 50 50 50;
color: blue;
}
color {
bounds: 50 50 50 50;
color: yellow;
}
}

View File

@ -0,0 +1,12 @@
color {
bounds: 0 0 25 50;
}
color {
bounds: 25 0 25 50;
}
color {
bounds: 50 0 25 50;
}
color {
bounds: 75 0 25 50;
}

View File

@ -0,0 +1,12 @@
color {
bounds: 75 0 25 50;
}
color {
bounds: 50 0 25 50;
}
color {
bounds: 25 0 25 50;
}
color {
bounds: 0 0 25 50;
}

View File

@ -0,0 +1,12 @@
color {
bounds: 75 0 50 50;
}
color {
bounds: 50 0 50 50;
}
color {
bounds: 25 0 50 50;
}
color {
bounds: 0 0 50 50;
}

View File

@ -0,0 +1,12 @@
color {
bounds: 0 0 50 50;
}
color {
bounds: 25 0 50 50;
}
color {
bounds: 50 0 50 50;
}
color {
bounds: 75 0 50 50;
}

View File

@ -0,0 +1,8 @@
color {
bounds: 0 0 50 50;
color: lime;
}
color {
bounds: 40 10 50 50;
color: red;
}

View File

@ -0,0 +1,7 @@
texture-scale {
bounds: 3 0 1.5 1;
}
texture-scale {
bounds: 0 3 1 1.5;
}

View File

@ -0,0 +1,12 @@
color {
bounds: 0 0 50 25;
}
color {
bounds: 0 25 50 25;
}
color {
bounds: 0 50 50 25;
}
color {
bounds: 0 75 50 25;
}

View File

@ -0,0 +1,12 @@
color {
bounds: 0 75 50 25;
}
color {
bounds: 0 50 50 25;
}
color {
bounds: 0 25 50 25;
}
color {
bounds: 0 0 50 25;
}

View File

@ -0,0 +1,12 @@
color {
bounds: 0 0 50 50;
}
color {
bounds: 0 25 50 50;
}
color {
bounds: 0 50 50 50;
}
color {
bounds: 0 75 50 50;
}

View File

@ -0,0 +1,8 @@
color {
bounds: 0 0 50 50;
color: lime;
}
color {
bounds: 10 40 50 50;
color: red;
}

View File

@ -177,7 +177,7 @@ file_info (const char *filename)
unsigned int total = 0;
unsigned int namelen = 0;
unsigned int depth = 0;
graphene_rect_t bounds;
graphene_rect_t bounds, opaque;
node = load_node_file (filename);
@ -202,6 +202,15 @@ file_info (const char *filename)
gsk_render_node_get_bounds (node, &bounds);
g_print (_("Bounds: %g x %g\n"), bounds.size.width, bounds.size.height);
g_print (_("Origin: %g %g\n"), bounds.origin.x, bounds.origin.y);
if (gsk_render_node_get_opaque_rect (node, &opaque))
{
g_print (_("Opaque part: %g %g, %g x %g (%.0f%%)\n"),
opaque.origin.x, opaque.origin.y,
opaque.size.width, opaque.size.height,
100 * (opaque.size.width * opaque.size.height) / (bounds.size.width * bounds.size.height));
}
else
g_print (_("Opaque part: none\n"));
gsk_render_node_unref (node);
}