gpu: Add a repeat node renderer

The ubershader has some corner cases where it can't be used, in
particular when the child is massively larger than the repeat node and
the repeat node is used to clip lots of the source.
This commit is contained in:
Benjamin Otte
2023-12-23 16:56:19 +01:00
parent db93fd6b7b
commit ee54f5a50a
2 changed files with 221 additions and 11 deletions

View File

@@ -1137,16 +1137,21 @@ gsk_gpu_node_processor_add_without_opacity (GskGpuNodeProcessor *self,
}
static void
gsk_gpu_node_processor_add_clip_node (GskGpuNodeProcessor *self,
GskRenderNode *node)
gsk_gpu_node_processor_add_node_clipped (GskGpuNodeProcessor *self,
GskRenderNode *node,
const graphene_rect_t *clip_bounds)
{
GskRenderNode *child;
GskGpuClip old_clip;
graphene_rect_t clip;
cairo_rectangle_int_t scissor;
child = gsk_clip_node_get_child (node);
graphene_rect_offset_r (gsk_clip_node_get_clip (node),
if (gsk_rect_contains_rect (clip_bounds, &node->bounds))
{
gsk_gpu_node_processor_add_node (self, node);
return;
}
graphene_rect_offset_r (clip_bounds,
self->offset.x, self->offset.y,
&clip);
@@ -1177,7 +1182,7 @@ gsk_gpu_node_processor_add_clip_node (GskGpuNodeProcessor *self,
self->scissor = scissor;
self->pending_globals |= GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_CLIP;
gsk_gpu_node_processor_add_node (self, child);
gsk_gpu_node_processor_add_node (self, node);
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
self->scissor = old_scissor;
@@ -1190,7 +1195,7 @@ gsk_gpu_node_processor_add_clip_node (GskGpuNodeProcessor *self,
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
gsk_gpu_node_processor_add_node (self, child);
gsk_gpu_node_processor_add_node (self, node);
self->scissor = old_scissor;
self->pending_globals |= GSK_GPU_GLOBAL_SCISSOR;
@@ -1210,13 +1215,13 @@ gsk_gpu_node_processor_add_clip_node (GskGpuNodeProcessor *self,
0,
0,
NULL,
child,
node,
&tex_rect);
if (image)
{
gsk_gpu_node_processor_image_op (self,
image,
&node->bounds,
clip_bounds,
&tex_rect);
g_object_unref (image);
}
@@ -1232,13 +1237,22 @@ gsk_gpu_node_processor_add_clip_node (GskGpuNodeProcessor *self,
self->pending_globals |= GSK_GPU_GLOBAL_CLIP;
gsk_gpu_node_processor_add_node (self, child);
gsk_gpu_node_processor_add_node (self, node);
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
self->pending_globals |= GSK_GPU_GLOBAL_CLIP;
}
}
static void
gsk_gpu_node_processor_add_clip_node (GskGpuNodeProcessor *self,
GskRenderNode *node)
{
gsk_gpu_node_processor_add_node_clipped (self,
gsk_clip_node_get_child (node),
gsk_clip_node_get_clip (node));
}
static gboolean
gsk_gpu_node_processor_create_clip_pattern (GskGpuPatternWriter *self,
GskRenderNode *node)
@@ -2499,6 +2513,193 @@ gsk_gpu_node_processor_create_color_matrix_pattern (GskGpuPatternWriter *self,
return TRUE;
}
static void
gsk_gpu_node_processor_repeat_tile (GskGpuNodeProcessor *self,
const graphene_rect_t *rect,
float x,
float y,
GskRenderNode *child,
const graphene_rect_t *child_bounds)
{
GskGpuImage *image;
graphene_rect_t clipped_child_bounds, offset_rect;
guint32 descriptor;
gsk_rect_init_offset (&offset_rect,
rect,
- x * child_bounds->size.width,
- y * child_bounds->size.height);
if (!gsk_rect_intersection (&offset_rect, child_bounds, &clipped_child_bounds))
{
/* The math has gone wrong probably, someone should look at this. */
g_warn_if_reached ();
return;
}
/* Take advantage of caching machinery if we can.
* If the sizes don't match, we can't though, because we need to
* create the right sized image for tiling.
*/
if (gsk_rect_equal (&child->bounds, &clipped_child_bounds))
{
graphene_rect_t tex_rect;
image = gsk_gpu_node_processor_get_node_as_image (self,
0,
GSK_GPU_IMAGE_STRAIGHT_ALPHA,
&clipped_child_bounds,
child,
&tex_rect);
/* The math went wrong */
g_warn_if_fail (gsk_rect_equal (&tex_rect, &clipped_child_bounds));
}
else
{
GSK_DEBUG (FALLBACK, "Offscreening node '%s' for tiling", g_type_name_from_instance ((GTypeInstance *) child));
image = gsk_gpu_render_pass_op_offscreen (self->frame,
&self->scale,
&clipped_child_bounds,
child);
}
g_return_if_fail (image);
descriptor = gsk_gpu_node_processor_add_image (self, image, GSK_GPU_SAMPLER_REPEAT);
gsk_gpu_texture_op (self->frame,
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, rect),
self->desc,
descriptor,
rect,
&self->offset,
&GRAPHENE_RECT_INIT (
clipped_child_bounds.origin.x - x * child_bounds->size.width,
clipped_child_bounds.origin.y - y * child_bounds->size.height,
clipped_child_bounds.size.width,
clipped_child_bounds.size.height
));
g_object_unref (image);
}
static void
gsk_gpu_node_processor_add_repeat_node (GskGpuNodeProcessor *self,
GskRenderNode *node)
{
GskRenderNode *child;
const graphene_rect_t *child_bounds;
graphene_rect_t bounds;
float tile_left, tile_right, tile_top, tile_bottom;
child = gsk_repeat_node_get_child (node);
child_bounds = gsk_repeat_node_get_child_bounds (node);
if (gsk_rect_is_empty (child_bounds))
return;
gsk_gpu_node_processor_get_clip_bounds (self, &bounds);
if (!gsk_rect_intersection (&bounds, &node->bounds, &bounds))
return;
tile_left = (bounds.origin.x - child_bounds->origin.x) / child_bounds->size.width;
tile_right = (bounds.origin.x + bounds.size.width - child_bounds->origin.x) / child_bounds->size.width;
tile_top = (bounds.origin.y - child_bounds->origin.y) / child_bounds->size.height;
tile_bottom = (bounds.origin.y + bounds.size.height - child_bounds->origin.y) / child_bounds->size.height;
/* the 1st check tests that a tile fully fits into the bounds,
* the 2nd check is to catch the case where it fits exactly */
if (ceilf (tile_left) < floorf (tile_right) &&
bounds.size.width > child_bounds->size.width)
{
if (ceilf (tile_top) < floorf (tile_bottom) &&
bounds.size.height > child_bounds->size.height)
{
/* tile in both directions */
gsk_gpu_node_processor_repeat_tile (self,
&bounds,
ceilf (tile_left),
ceilf (tile_top),
child,
child_bounds);
}
else
{
/* tile horizontally, repeat vertically */
float y;
for (y = floorf (tile_top); y < ceilf (tile_bottom); y++)
{
float start_y = MAX (bounds.origin.y,
child_bounds->origin.y + y * child_bounds->size.height);
float end_y = MIN (bounds.origin.y + bounds.size.height,
child_bounds->origin.y + (y + 1) * child_bounds->size.height);
gsk_gpu_node_processor_repeat_tile (self,
&GRAPHENE_RECT_INIT (
bounds.origin.x,
start_y,
bounds.size.width,
end_y - start_y
),
ceilf (tile_left),
y,
child,
child_bounds);
}
}
}
else if (ceilf (tile_top) < floorf (tile_bottom) &&
bounds.size.height > child_bounds->size.height)
{
/* repeat horizontally, tile vertically */
float x;
for (x = floorf (tile_left); x < ceilf (tile_right); x++)
{
float start_x = MAX (bounds.origin.x,
child_bounds->origin.x + x * child_bounds->size.width);
float end_x = MIN (bounds.origin.x + bounds.size.width,
child_bounds->origin.x + (x + 1) * child_bounds->size.width);
gsk_gpu_node_processor_repeat_tile (self,
&GRAPHENE_RECT_INIT (
start_x,
bounds.origin.y,
end_x - start_x,
bounds.size.height
),
x,
ceilf (tile_top),
child,
child_bounds);
}
}
else
{
/* repeat in both directions */
graphene_point_t old_offset, offset;
graphene_rect_t clip_bounds;
float x, y;
old_offset = self->offset;
for (x = floorf (tile_left); x < ceilf (tile_right); x++)
{
offset.x = x * child_bounds->size.width;
for (y = floorf (tile_top); y < ceilf (tile_bottom); y++)
{
offset.y = y * child_bounds->size.height;
self->offset = GRAPHENE_POINT_INIT (old_offset.x + offset.x, old_offset.y + offset.y);
clip_bounds = GRAPHENE_RECT_INIT (bounds.origin.x - offset.x,
bounds.origin.y - offset.y,
bounds.size.width,
bounds.size.height);
if (!gsk_rect_intersection (&clip_bounds, child_bounds, &clip_bounds))
continue;
gsk_gpu_node_processor_add_node_clipped (self,
child,
&clip_bounds);
}
}
self->offset = old_offset;
}
}
static gboolean
gsk_gpu_node_processor_create_repeat_pattern (GskGpuPatternWriter *self,
GskRenderNode *node)
@@ -2729,7 +2930,7 @@ static const struct
[GSK_REPEAT_NODE] = {
0,
GSK_GPU_HANDLE_OPACITY,
gsk_gpu_node_processor_add_node_as_pattern,
gsk_gpu_node_processor_add_repeat_node,
gsk_gpu_node_processor_create_repeat_pattern
},
[GSK_CLIP_NODE] = {

View File

@@ -23,6 +23,15 @@ gsk_rect_init_from_rect (graphene_rect_t *r,
gsk_rect_init (r, r1->origin.x, r1->origin.y, r1->size.width, r1->size.height);
}
static inline void
gsk_rect_init_offset (graphene_rect_t *r,
const graphene_rect_t *src,
float dx,
float dy)
{
gsk_rect_init (r, src->origin.x + dx, src->origin.y + dy, src->size.width, src->size.height);
}
static inline gboolean G_GNUC_PURE
gsk_rect_contains_rect (const graphene_rect_t *r1,
const graphene_rect_t *r2)