#include "config.h"
#include "gskgpuocclusionprivate.h"
#include "gskgpuclearopprivate.h"
#include "gskgpuframeprivate.h"
#include "gskgpuimageprivate.h"
#include "gskgpunodeprocessorprivate.h"
#include "gskrectprivate.h"
#include "gskrendernodeprivate.h"
#include "gdkcairoprivate.h"
/* the amount of pixels for us to potentially save to warrant
* carving out a rectangle for an extra render pass
*/
#define MIN_PIXELS_FOR_OCCLUSION_PASS 1000 * 100
/* the amount of the whole image for us to potentially save to warrant
* carving out a rectangle for an extra render pass
*/
#define MIN_PERCENTAGE_FOR_OCCLUSION_PASS 10
struct _GskGpuOcclusion
{
GskGpuFrame *frame;
GskGpuImage *target;
GdkColorState *target_color_state;
GskRenderPassType pass_type;
cairo_region_t *clip_region;
graphene_rect_t viewport;
cairo_rectangle_int_t device_clip;
GskGpuTransform transform;
GskGpuRenderPass *pass;
GskGpuRenderPassClipStorage scissor_storage;
float background_color[4 ];
guint has_background : 1 ;
guint has_started_rendering : 1 ;
};
static gboolean G_GNUC_WARN_UNUSED_RESULT
gsk_gpu_occlusion_user_to_device (GskGpuOcclusion *self,
const graphene_rect_t *user,
GskRectSnap snap,
cairo_rectangle_int_t *device)
{
graphene_rect_t tmp;
gsk_gpu_transform_transform_rect (&self->transform,
user,
&tmp);
/* FIXME: rotations? */
if (snap != GSK_SNAP_NONE)
{
if (!gsk_rect_snap (&tmp, snap, &tmp))
return FALSE ;
}
return gsk_rect_to_cairo_shrink (&tmp, device);
}
static void
gsk_gpu_occlusion_init (GskGpuOcclusion *self,
GskGpuFrame *frame,
GskGpuImage *target,
GdkColorState *target_color_state,
GskRenderPassType pass_type,
cairo_region_t *clip_region,
const graphene_rect_t *viewport)
{
self->frame = frame;
self->target = target;
self->target_color_state = target_color_state;
self->pass_type = pass_type;
self->clip_region = clip_region;
self->viewport = *viewport;
self->device_clip = (cairo_rectangle_int_t) { 0 , 0 , 0 , 0 };
gsk_gpu_transform_init (&self->transform,
GDK_DIHEDRAL_NORMAL,
&GRAPHENE_SIZE_INIT (gsk_gpu_image_get_width (target) / viewport->size.width,
gsk_gpu_image_get_height (target) / viewport->size.height),
&GRAPHENE_POINT_INIT (-viewport->origin.x,
-viewport->origin.y));
self->has_background = FALSE ;
self->has_started_rendering = FALSE ;
}
static void
gsk_gpu_occlusion_finish (GskGpuOcclusion *self)
{
cairo_region_destroy (self->clip_region);
if (self->has_started_rendering)
{
gsk_gpu_render_pass_free (self->pass);
}
}
GskGpuFrame *
gsk_gpu_occlusion_get_frame (GskGpuOcclusion *self)
{
return self->frame;
}
gboolean
gsk_gpu_occlusion_push_transform (GskGpuOcclusion *self,
GskTransform *transform,
GskGpuTransform *out_save)
{
*out_save = self->transform;
return gsk_gpu_transform_transform (&self->transform, transform);
}
void
gsk_gpu_occlusion_pop_transform (GskGpuOcclusion *self,
const GskGpuTransform *saved)
{
self->transform = *saved;
}
static gsize
gsk_gpu_occlusion_get_min_pixels (GskGpuOcclusion *self)
{
gsize result;
result = gsk_gpu_image_get_width (self->target) * gsk_gpu_image_get_height (self->target) *
MIN_PERCENTAGE_FOR_OCCLUSION_PASS / 100 ;
result = MAX (result, MIN_PIXELS_FOR_OCCLUSION_PASS);
return result;
}
gboolean
gsk_gpu_occlusion_push_clip (GskGpuOcclusion *self,
const graphene_rect_t *clip,
GskRectSnap snap,
cairo_rectangle_int_t *out_save)
{
cairo_rectangle_int_t device;
if (!gsk_gpu_occlusion_user_to_device (self, clip, snap, &device))
return FALSE ;
if (!gdk_rectangle_intersect (&device, &self->device_clip, &device))
return FALSE ;
/* Only check pixel amount if the rect got smaller.
* This way , we guarantee that covering the full rectangle
* will always start an occlusion pass */
if ((device.width < self->device_clip.width || device.height < self->device_clip.height) &&
device.width * device.height < gsk_gpu_occlusion_get_min_pixels (self))
return FALSE ;
*out_save = self->device_clip;
self->device_clip = device;
return TRUE ;
}
void
gsk_gpu_occlusion_pop_clip (GskGpuOcclusion *self,
const cairo_rectangle_int_t *saved)
{
self->device_clip = *saved;
}
static void
gsk_gpu_occlusion_begin_rendering (GskGpuOcclusion *self,
float *clear_color) /* float[4] or NULL */
{
GskDebugProfile *profile;
profile = gsk_gpu_frame_get_profile (self->frame);
if (profile)
{
profile->self.n_bases++;
profile->self.base_pixels += self->device_clip.width * self->device_clip.height;
}
if (self->has_started_rendering)
{
gsk_gpu_render_pass_push_clip_device_rect (self->pass, &self->device_clip, &self->scissor_storage);
gsk_gpu_render_pass_set_transform (self->pass, &self->transform);
if (clear_color &&
(!self->has_background ||
(self->has_background &&
(clear_color[0 ] != self->background_color[0 ] ||
clear_color[1 ] != self->background_color[1 ] ||
clear_color[2 ] != self->background_color[2 ] ||
clear_color[3 ] != self->background_color[3 ]))))
{
gsk_gpu_clear_op (self->frame, &self->device_clip, clear_color);
}
}
else
{
GskGpuLoadOp load_op;
cairo_rectangle_int_t extents;
if (!gdk_cairo_region_is_rectangle (self->clip_region))
{
self->has_background = FALSE ;
load_op = GSK_GPU_LOAD_OP_LOAD;
}
else if (clear_color)
{
self->background_color[0 ] = clear_color[0 ];
self->background_color[1 ] = clear_color[1 ];
self->background_color[2 ] = clear_color[2 ];
self->background_color[3 ] = clear_color[3 ];
self->has_background = TRUE ;
load_op = GSK_GPU_LOAD_OP_CLEAR;
}
else
{
self->has_background = FALSE ;
load_op = GSK_GPU_LOAD_OP_DONT_CARE;
}
cairo_region_get_extents (self->clip_region, &extents);
self->has_started_rendering = TRUE ;
self->pass = gsk_gpu_render_pass_new (self->frame,
self->target,
self->target_color_state,
self->pass_type,
load_op,
clear_color,
&extents,
&self->viewport);
gsk_gpu_render_pass_push_clip_device_rect (self->pass, &self->device_clip, &self->scissor_storage);
gsk_gpu_render_pass_set_transform (self->pass, &self->transform);
if (!self->has_background && clear_color)
gsk_gpu_clear_op (self->frame, &self->pass->scissor, clear_color);
}
}
GskGpuRenderPass *
gsk_gpu_occlusion_begin_rendering_whatever (GskGpuOcclusion *self)
{
gsk_gpu_occlusion_begin_rendering (self, NULL);
return self->pass;
}
GskGpuRenderPass *
gsk_gpu_occlusion_begin_rendering_transparent (GskGpuOcclusion *self)
{
gsk_gpu_occlusion_begin_rendering (self, (float [4 ]) { 0 , 0 , 0 , 0 });
return self->pass;
}
GskGpuRenderPass *
gsk_gpu_occlusion_begin_rendering_color (GskGpuOcclusion *self,
const GdkColor *color)
{
GdkColor convert;
gdk_color_convert (&convert, self->target_color_state, color);
gsk_gpu_occlusion_begin_rendering (self, convert.values);
return self->pass;
}
GskGpuRenderPass *
gsk_gpu_occlusion_try_node_untracked (GskGpuOcclusion *self,
GskRenderNode *node)
{
graphene_rect_t opaque;
cairo_rectangle_int_t prev_clip;
GskGpuRenderPass *result;
/* 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 ||
!gsk_render_node_get_opaque_rect (node, &opaque))
return NULL;
if (!gsk_gpu_occlusion_push_clip (self, &opaque, GSK_RECT_SNAP_NONE, &prev_clip))
return NULL;
result = GSK_RENDER_NODE_GET_CLASS (node)->occlusion (node, self);
if (result == NULL)
{
gsk_gpu_occlusion_pop_clip (self, &prev_clip);
}
return result;
}
GskGpuRenderPass *
gsk_gpu_occlusion_try_node (GskGpuOcclusion *self,
GskRenderNode *node,
gsize pos)
{
GskGpuRenderPass *result;
gsk_gpu_frame_start_node (self->frame, node, pos);
result = gsk_gpu_occlusion_try_node_untracked (self, node);
gsk_gpu_frame_end_node (self->frame);
return result;
}
static gboolean
gsk_gpu_occlusion_run (GskGpuOcclusion *self,
cairo_rectangle_int_t *device_clip,
GskRenderNode *node)
{
gboolean result;
self->device_clip = *device_clip;
if (gsk_gpu_occlusion_try_node_untracked (self, node))
result = TRUE ;
else
{
GskGpuRenderPass *pass;
pass = gsk_gpu_occlusion_begin_rendering_transparent (self);
gsk_gpu_node_processor_add_first_node_untracked (pass, node);
result = FALSE ;
}
/* NB: not the passed in device clip, we might have shrunk the region */
cairo_region_subtract_rectangle (self->clip_region, &self->device_clip);
gsk_gpu_render_pass_pop_clip_device_rect (self->pass, &self->scissor_storage);
return result;
}
void
gsk_gpu_occlusion_render_node (GskGpuFrame *frame,
GskGpuImage *target,
GdkColorState *target_color_state,
GskRenderPassType pass_type,
cairo_region_t *clip,
const graphene_rect_t *viewport,
GskRenderNode *node)
{
GskGpuOcclusion self;
int i, n, best, best_size;
cairo_rectangle_int_t rect;
gboolean do_culling;
gsk_gpu_occlusion_init (&self,
frame,
target,
target_color_state,
pass_type,
clip,
viewport);
do_culling = gsk_gpu_frame_should_optimize (frame, GSK_GPU_OPTIMIZE_OCCLUSION_CULLING);
while (do_culling &&
(n = cairo_region_num_rectangles (clip)) > 0 )
{
best = -1 ;
best_size = 0 ;
for (i = 0 ; i < n; i++)
{
cairo_region_get_rectangle (clip, i, &rect);
if (rect.width * rect.height > best_size)
{
best = i;
best_size = rect.width * rect.height;
}
}
if (best_size < MIN_PIXELS_FOR_OCCLUSION_PASS)
break ;
cairo_region_get_rectangle (clip, best, &rect);
if (!gsk_gpu_occlusion_run (&self, &rect, node))
break ;
}
while (cairo_region_num_rectangles (clip) > 0 )
{
cairo_region_get_rectangle (clip, 0 , &rect);
gsk_gpu_occlusion_run (&self, &rect, node);
}
gsk_gpu_occlusion_finish (&self);
}
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland