mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-09 10:20:07 +00:00
d754f5c1b4
Make non-srgb css colors work for gradients See merge request GNOME/gtk!7584
4538 lines
169 KiB
C
4538 lines
169 KiB
C
#include "config.h"
|
|
|
|
#include "gskgpunodeprocessorprivate.h"
|
|
|
|
#include "gskgpuborderopprivate.h"
|
|
#include "gskgpuboxshadowopprivate.h"
|
|
#include "gskgpublendmodeopprivate.h"
|
|
#include "gskgpublendopprivate.h"
|
|
#include "gskgpublitopprivate.h"
|
|
#include "gskgpubluropprivate.h"
|
|
#include "gskgpucacheprivate.h"
|
|
#include "gskgpuclearopprivate.h"
|
|
#include "gskgpuclipprivate.h"
|
|
#include "gskgpucolorizeopprivate.h"
|
|
#include "gskgpucolormatrixopprivate.h"
|
|
#include "gskgpucoloropprivate.h"
|
|
#include "gskgpuconicgradientopprivate.h"
|
|
#include "gskgpuconvertopprivate.h"
|
|
#include "gskgpuconvertcicpopprivate.h"
|
|
#include "gskgpucrossfadeopprivate.h"
|
|
#include "gskgpudeviceprivate.h"
|
|
#include "gskgpuframeprivate.h"
|
|
#include "gskgpuglobalsopprivate.h"
|
|
#include "gskgpuimageprivate.h"
|
|
#include "gskgpulineargradientopprivate.h"
|
|
#include "gskgpumaskopprivate.h"
|
|
#include "gskgpumipmapopprivate.h"
|
|
#include "gskgpuradialgradientopprivate.h"
|
|
#include "gskgpurenderpassopprivate.h"
|
|
#include "gskgpuroundedcoloropprivate.h"
|
|
#include "gskgpuscissoropprivate.h"
|
|
#include "gskgputextureopprivate.h"
|
|
#include "gskgpuuploadopprivate.h"
|
|
|
|
#include "gskcairoblurprivate.h"
|
|
#include "gskdebugprivate.h"
|
|
#include "gskpath.h"
|
|
#include "gskrectprivate.h"
|
|
#include "gskrendernodeprivate.h"
|
|
#include "gskroundedrectprivate.h"
|
|
#include "gskstrokeprivate.h"
|
|
#include "gsktransformprivate.h"
|
|
#include "gskprivate.h"
|
|
|
|
#include "gdk/gdkcolorstateprivate.h"
|
|
#include "gdk/gdkcairoprivate.h"
|
|
#include "gdk/gdkmemorytextureprivate.h"
|
|
#include "gdk/gdkrgbaprivate.h"
|
|
#include "gdk/gdksubsurfaceprivate.h"
|
|
#include "gdk/gdktextureprivate.h"
|
|
|
|
/* the epsilon we allow pixels to be off due to rounding errors.
|
|
* Chosen rather randomly.
|
|
*/
|
|
#define EPSILON 0.001
|
|
|
|
/* 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
|
|
|
|
/* A note about coordinate systems
|
|
*
|
|
* The rendering code keeps track of multiple coordinate systems to optimize rendering as
|
|
* much as possible and in the coordinate system it makes most sense in.
|
|
* Sometimes there are cases where GL requires a certain coordinate system, too.
|
|
*
|
|
* 1. the node coordinate system
|
|
* This is the coordinate system of the rendernode. It is basically not used outside of
|
|
* looking at the node and basically never hits the GPU (it does for paths). We immediately
|
|
* convert it to:
|
|
*
|
|
* 2. the basic coordinate system
|
|
* convert on CPU: NodeProcessor.offset
|
|
* convert on GPU: ---
|
|
* This is the coordinate system we emit vertex state in, the clip is tracked here.
|
|
* The main benefit is that most transform nodes only change the offset, so we can avoid
|
|
* updating any state in this coordinate system when that happens.
|
|
*
|
|
* 3. the scaled coordinate system
|
|
* converts on CPU: NodeProcessor.scale
|
|
* converts on GPU: GSK_GLOBAL_SCALE
|
|
* This includes the current scale of the transform. It is usually equal to the scale factor
|
|
* of the window we are rendering to (which is bad because devs without hidpi screens can
|
|
* forget this and then everyone else will see bugs). We make decisions about pixel sizes in
|
|
* this coordinate system, like picking glyphs from the glyph cache or the sizes of offscreens
|
|
* for offscreen rendering.
|
|
*
|
|
* 4. the device coordinate system
|
|
* converts on CPU: NodeProcessor.modelview
|
|
* converts on GPU: ---
|
|
* The scissor rect is tracked in this coordinate system. It represents the actual device pixels.
|
|
* A bunch of optimizations (like glScissor() and glClear()) can be done here, so in the case
|
|
* that modelview == NULL and we end up with integer coordinates (because pixels), we try to go
|
|
* here.
|
|
* This coordinate system does not exist on shaders as they rarely reason about pixels, and if
|
|
* they need to, they can ask the fragment shader via gl_FragCoord.
|
|
*
|
|
* 5. the GL coordinate system
|
|
* converts on CPU: NodeProcessor.projection
|
|
* converts on GPU: GSK_GLOBAL_MVP (from scaled coordinate system)
|
|
* This coordinate system is what GL (or Vulkan) expect coordinates to appear in, and is usually
|
|
* (-1, -1) => (1, 1), but may be flipped etc depending on the render target. The CPU essentially
|
|
* never uses it, other than to allow the vertex shaders to emit its vertices.
|
|
*/
|
|
|
|
|
|
typedef struct _GskGpuNodeProcessor GskGpuNodeProcessor;
|
|
|
|
typedef enum {
|
|
/* The returned image will be sampled outside the bounds, so it is
|
|
* important that it returns the right values.
|
|
* In particular, opaque textures must ensure they return transparency
|
|
* and images must not be contained in an atlas.
|
|
*/
|
|
GSK_GPU_AS_IMAGE_SAMPLED_OUT_OF_BOUNDS = (1 << 0),
|
|
} GskGpuAsImageFlags;
|
|
|
|
typedef enum {
|
|
GSK_GPU_GLOBAL_MATRIX = (1 << 0),
|
|
GSK_GPU_GLOBAL_SCALE = (1 << 1),
|
|
GSK_GPU_GLOBAL_CLIP = (1 << 2),
|
|
GSK_GPU_GLOBAL_SCISSOR = (1 << 3),
|
|
GSK_GPU_GLOBAL_BLEND = (1 << 4),
|
|
} GskGpuGlobals;
|
|
|
|
struct _GskGpuNodeProcessor
|
|
{
|
|
GskGpuFrame *frame;
|
|
GdkColorState *ccs;
|
|
cairo_rectangle_int_t scissor;
|
|
GskGpuBlend blend;
|
|
graphene_point_t offset;
|
|
graphene_matrix_t projection;
|
|
graphene_vec2_t scale;
|
|
GskTransform *modelview;
|
|
GskGpuClip clip;
|
|
float opacity;
|
|
|
|
GskGpuGlobals pending_globals;
|
|
};
|
|
|
|
typedef struct _GskGpuFirstNodeInfo GskGpuFirstNodeInfo;
|
|
|
|
struct _GskGpuFirstNodeInfo
|
|
{
|
|
GskGpuImage *target;
|
|
cairo_rectangle_int_t extents;
|
|
GskRenderPassType pass_type;
|
|
gsize min_occlusion_pixels;
|
|
float background_color[4];
|
|
|
|
guint whole_area : 1;
|
|
guint has_background : 1;
|
|
guint has_started_rendering : 1;
|
|
};
|
|
|
|
static void gsk_gpu_node_processor_add_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node);
|
|
static gboolean gsk_gpu_node_processor_add_first_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node);
|
|
static GskGpuImage * gsk_gpu_get_node_as_image (GskGpuFrame *frame,
|
|
GskGpuAsImageFlags flags,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds);
|
|
|
|
static void
|
|
gsk_gpu_node_processor_finish (GskGpuNodeProcessor *self)
|
|
{
|
|
g_clear_pointer (&self->modelview, gsk_transform_unref);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_init (GskGpuNodeProcessor *self,
|
|
GskGpuFrame *frame,
|
|
GskGpuImage *target,
|
|
GdkColorState *ccs,
|
|
const cairo_rectangle_int_t *clip,
|
|
const graphene_rect_t *viewport)
|
|
{
|
|
gsize width, height;
|
|
|
|
width = gsk_gpu_image_get_width (target);
|
|
height = gsk_gpu_image_get_height (target);
|
|
|
|
self->frame = frame;
|
|
self->ccs = ccs;
|
|
|
|
self->scissor = *clip;
|
|
self->blend = GSK_GPU_BLEND_OVER;
|
|
if (clip->x == 0 && clip->y == 0 && clip->width == width && clip->height == height)
|
|
{
|
|
gsk_gpu_clip_init_empty (&self->clip, &GRAPHENE_RECT_INIT (0, 0, viewport->size.width, viewport->size.height));
|
|
}
|
|
else
|
|
{
|
|
float scale_x = viewport->size.width / width;
|
|
float scale_y = viewport->size.height / height;
|
|
gsk_gpu_clip_init_empty (&self->clip,
|
|
&GRAPHENE_RECT_INIT (
|
|
scale_x * clip->x,
|
|
scale_y * clip->y,
|
|
scale_x * clip->width,
|
|
scale_y * clip->height
|
|
));
|
|
}
|
|
|
|
self->modelview = NULL;
|
|
gsk_gpu_image_get_projection_matrix (target, &self->projection);
|
|
graphene_vec2_init (&self->scale,
|
|
width / viewport->size.width,
|
|
height / viewport->size.height);
|
|
self->offset = GRAPHENE_POINT_INIT (-viewport->origin.x,
|
|
-viewport->origin.y);
|
|
self->opacity = 1.0;
|
|
self->pending_globals = GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_BLEND;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_emit_globals_op (GskGpuNodeProcessor *self)
|
|
{
|
|
graphene_matrix_t mvp;
|
|
|
|
if (self->modelview)
|
|
{
|
|
gsk_transform_to_matrix (self->modelview, &mvp);
|
|
graphene_matrix_multiply (&mvp, &self->projection, &mvp);
|
|
}
|
|
else
|
|
graphene_matrix_init_from_matrix (&mvp, &self->projection);
|
|
|
|
gsk_gpu_globals_op (self->frame,
|
|
&self->scale,
|
|
&mvp,
|
|
&self->clip.rect);
|
|
|
|
self->pending_globals &= ~(GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_emit_scissor_op (GskGpuNodeProcessor *self)
|
|
{
|
|
gsk_gpu_scissor_op (self->frame,
|
|
&self->scissor);
|
|
self->pending_globals &= ~GSK_GPU_GLOBAL_SCISSOR;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_emit_blend_op (GskGpuNodeProcessor *self)
|
|
{
|
|
gsk_gpu_blend_op (self->frame, self->blend);
|
|
self->pending_globals &= ~GSK_GPU_GLOBAL_BLEND;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_sync_globals (GskGpuNodeProcessor *self,
|
|
GskGpuGlobals ignored)
|
|
{
|
|
GskGpuGlobals required;
|
|
|
|
required = self->pending_globals & ~ignored;
|
|
|
|
if (required & (GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP))
|
|
gsk_gpu_node_processor_emit_globals_op (self);
|
|
if (required & GSK_GPU_GLOBAL_SCISSOR)
|
|
gsk_gpu_node_processor_emit_scissor_op (self);
|
|
if (required & GSK_GPU_GLOBAL_BLEND)
|
|
gsk_gpu_node_processor_emit_blend_op (self);
|
|
}
|
|
|
|
static inline GskGpuColorStates
|
|
gsk_gpu_node_processor_color_states_explicit (GskGpuNodeProcessor *self,
|
|
GdkColorState *alt,
|
|
gboolean alt_premultiplied)
|
|
{
|
|
return gsk_gpu_color_states_create (self->ccs,
|
|
TRUE,
|
|
alt,
|
|
alt_premultiplied);
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_node_processor_init_draw (GskGpuNodeProcessor *self,
|
|
GskGpuFrame *frame,
|
|
GdkColorState *ccs,
|
|
GdkMemoryDepth depth,
|
|
const graphene_vec2_t *scale,
|
|
const graphene_rect_t *viewport)
|
|
{
|
|
GskGpuImage *image;
|
|
cairo_rectangle_int_t area;
|
|
|
|
area.x = 0;
|
|
area.y = 0;
|
|
area.width = MAX (1, ceilf (graphene_vec2_get_x (scale) * viewport->size.width - EPSILON));
|
|
area.height = MAX (1, ceilf (graphene_vec2_get_y (scale) * viewport->size.height - EPSILON));
|
|
|
|
image = gsk_gpu_device_create_offscreen_image (gsk_gpu_frame_get_device (frame),
|
|
FALSE,
|
|
gdk_memory_depth_get_format (depth),
|
|
gdk_memory_depth_is_srgb (depth),
|
|
area.width, area.height);
|
|
if (image == NULL)
|
|
return NULL;
|
|
|
|
gsk_gpu_node_processor_init (self,
|
|
frame,
|
|
image,
|
|
ccs,
|
|
&area,
|
|
viewport);
|
|
|
|
gsk_gpu_render_pass_begin_op (frame,
|
|
image,
|
|
&area,
|
|
GSK_GPU_LOAD_OP_CLEAR,
|
|
GSK_VEC4_TRANSPARENT,
|
|
GSK_RENDER_PASS_OFFSCREEN);
|
|
|
|
return image;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_finish_draw (GskGpuNodeProcessor *self,
|
|
GskGpuImage *image)
|
|
{
|
|
gsk_gpu_render_pass_end_op (self->frame,
|
|
image,
|
|
GSK_RENDER_PASS_OFFSCREEN);
|
|
|
|
gsk_gpu_node_processor_finish (self);
|
|
}
|
|
|
|
static void
|
|
extract_scale_from_transform (GskTransform *transform,
|
|
float *out_scale_x,
|
|
float *out_scale_y)
|
|
{
|
|
switch (gsk_transform_get_fine_category (transform))
|
|
{
|
|
default:
|
|
g_assert_not_reached ();
|
|
case GSK_FINE_TRANSFORM_CATEGORY_IDENTITY:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_TRANSLATE:
|
|
*out_scale_x = 1.0f;
|
|
*out_scale_y = 1.0f;
|
|
return;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_AFFINE:
|
|
{
|
|
float scale_x, scale_y, dx, dy;
|
|
gsk_transform_to_affine (transform, &scale_x, &scale_y, &dx, &dy);
|
|
*out_scale_x = scale_x;
|
|
*out_scale_y = scale_y;
|
|
}
|
|
return;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D:
|
|
{
|
|
float skew_x, skew_y, scale_x, scale_y, angle, dx, dy;
|
|
gsk_transform_to_2d_components (transform,
|
|
&skew_x, &skew_y,
|
|
&scale_x, &scale_y,
|
|
&angle,
|
|
&dx, &dy);
|
|
*out_scale_x = fabs (scale_x);
|
|
*out_scale_y = fabs (scale_y);
|
|
}
|
|
return;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_UNKNOWN:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_ANY:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_3D:
|
|
{
|
|
graphene_quaternion_t rotation;
|
|
graphene_matrix_t matrix;
|
|
graphene_vec4_t perspective;
|
|
graphene_vec3_t translation;
|
|
graphene_vec3_t matrix_scale;
|
|
graphene_vec3_t shear;
|
|
|
|
gsk_transform_to_matrix (transform, &matrix);
|
|
graphene_matrix_decompose (&matrix,
|
|
&translation,
|
|
&matrix_scale,
|
|
&rotation,
|
|
&shear,
|
|
&perspective);
|
|
|
|
*out_scale_x = fabs (graphene_vec3_get_x (&matrix_scale));
|
|
*out_scale_y = fabs (graphene_vec3_get_y (&matrix_scale));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_rect_clip_to_device (GskGpuNodeProcessor *self,
|
|
const graphene_rect_t *src,
|
|
graphene_rect_t *dest)
|
|
{
|
|
graphene_rect_t transformed;
|
|
float scale_x = graphene_vec2_get_x (&self->scale);
|
|
float scale_y = graphene_vec2_get_y (&self->scale);
|
|
|
|
switch (gsk_transform_get_fine_category (self->modelview))
|
|
{
|
|
case GSK_FINE_TRANSFORM_CATEGORY_UNKNOWN:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_ANY:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_3D:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D:
|
|
return FALSE;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_AFFINE:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_TRANSLATE:
|
|
gsk_transform_transform_bounds (self->modelview, src, &transformed);
|
|
src = &transformed;
|
|
break;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_IDENTITY:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
dest->origin.x = src->origin.x * scale_x;
|
|
dest->origin.y = src->origin.y * scale_y;
|
|
dest->size.width = src->size.width * scale_x;
|
|
dest->size.height = src->size.height * scale_y;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_rect_device_to_clip (GskGpuNodeProcessor *self,
|
|
const graphene_rect_t *src,
|
|
graphene_rect_t *dest)
|
|
{
|
|
graphene_rect_t transformed;
|
|
float scale_x = graphene_vec2_get_x (&self->scale);
|
|
float scale_y = graphene_vec2_get_y (&self->scale);
|
|
|
|
switch (gsk_transform_get_fine_category (self->modelview))
|
|
{
|
|
case GSK_FINE_TRANSFORM_CATEGORY_UNKNOWN:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_ANY:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_3D:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D:
|
|
return FALSE;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_AFFINE:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_TRANSLATE:
|
|
{
|
|
GskTransform *inverse = gsk_transform_invert (gsk_transform_ref (self->modelview));
|
|
gsk_transform_transform_bounds (inverse, src, &transformed);
|
|
gsk_transform_unref (inverse);
|
|
src = &transformed;
|
|
}
|
|
break;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_IDENTITY:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
dest->origin.x = src->origin.x / scale_x;
|
|
dest->origin.y = src->origin.y / scale_y;
|
|
dest->size.width = src->size.width / scale_x;
|
|
dest->size.height = src->size.height / scale_y;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_rect_to_device_shrink (GskGpuNodeProcessor *self,
|
|
const graphene_rect_t *rect,
|
|
cairo_rectangle_int_t *int_rect)
|
|
{
|
|
graphene_rect_t tmp;
|
|
|
|
gsk_rect_init_offset (&tmp, rect, &self->offset);
|
|
|
|
if (!gsk_gpu_node_processor_rect_clip_to_device (self, &tmp, &tmp))
|
|
return FALSE;
|
|
|
|
gsk_rect_to_cairo_shrink (&tmp, int_rect);
|
|
|
|
return int_rect->width > 0 && int_rect->height > 0;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_rect_is_integer (GskGpuNodeProcessor *self,
|
|
const graphene_rect_t *rect,
|
|
cairo_rectangle_int_t *int_rect)
|
|
{
|
|
graphene_rect_t tmp;
|
|
|
|
if (!gsk_gpu_node_processor_rect_clip_to_device (self, rect, &tmp))
|
|
return FALSE;
|
|
|
|
gsk_rect_to_cairo_shrink (&tmp, int_rect);
|
|
|
|
return int_rect->x == tmp.origin.x
|
|
&& int_rect->y == tmp.origin.y
|
|
&& int_rect->width == tmp.size.width
|
|
&& int_rect->height == tmp.size.height;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_get_clip_bounds (GskGpuNodeProcessor *self,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
graphene_rect_t scissor;
|
|
|
|
if (gsk_gpu_node_processor_rect_device_to_clip (self,
|
|
&GSK_RECT_INIT_CAIRO (&self->scissor),
|
|
&scissor))
|
|
{
|
|
if (!gsk_rect_intersection (&scissor, &self->clip.rect.bounds, out_bounds))
|
|
{
|
|
g_warning ("Clipping is broken, everything is clipped, but we didn't early-exit.");
|
|
*out_bounds = self->clip.rect.bounds;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*out_bounds = self->clip.rect.bounds;
|
|
}
|
|
|
|
out_bounds->origin.x -= self->offset.x;
|
|
out_bounds->origin.y -= self->offset.y;
|
|
}
|
|
|
|
static gboolean G_GNUC_WARN_UNUSED_RESULT
|
|
gsk_gpu_node_processor_clip_node_bounds (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
graphene_rect_t tmp;
|
|
|
|
gsk_gpu_node_processor_get_clip_bounds (self, &tmp);
|
|
|
|
if (!gsk_rect_intersection (&tmp, &node->bounds, out_bounds))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_image_op (GskGpuNodeProcessor *self,
|
|
GskGpuImage *image,
|
|
GdkColorState *image_color_state,
|
|
GskGpuSampler sampler,
|
|
const graphene_rect_t *rect,
|
|
const graphene_rect_t *tex_rect)
|
|
{
|
|
gboolean straight_alpha;
|
|
|
|
g_assert (self->pending_globals == 0);
|
|
|
|
straight_alpha = gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_STRAIGHT_ALPHA;
|
|
|
|
if (!GDK_IS_DEFAULT_COLOR_STATE (image_color_state))
|
|
{
|
|
const GdkCicp *cicp = gdk_color_state_get_cicp (image_color_state);
|
|
|
|
g_assert (cicp != NULL);
|
|
|
|
gsk_gpu_convert_from_cicp_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, rect),
|
|
cicp,
|
|
gsk_gpu_color_states_create_cicp (self->ccs, TRUE, TRUE),
|
|
self->opacity,
|
|
straight_alpha,
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
sampler,
|
|
rect,
|
|
tex_rect
|
|
});
|
|
}
|
|
else if (straight_alpha ||
|
|
self->opacity < 1.0 ||
|
|
!gdk_color_state_equal (image_color_state, self->ccs))
|
|
{
|
|
gsk_gpu_convert_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, rect),
|
|
gsk_gpu_node_processor_color_states_explicit (self,
|
|
image_color_state,
|
|
TRUE),
|
|
self->opacity,
|
|
straight_alpha,
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
sampler,
|
|
rect,
|
|
tex_rect
|
|
});
|
|
}
|
|
else
|
|
{
|
|
gsk_gpu_texture_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, rect),
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
sampler,
|
|
rect,
|
|
tex_rect
|
|
});
|
|
}
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_node_processor_create_offscreen (GskGpuFrame *frame,
|
|
GdkColorState *ccs,
|
|
const graphene_vec2_t *scale,
|
|
const graphene_rect_t *viewport,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuImage *image;
|
|
cairo_rectangle_int_t area;
|
|
GdkMemoryDepth depth;
|
|
|
|
area.x = 0;
|
|
area.y = 0;
|
|
area.width = MAX (1, ceilf (graphene_vec2_get_x (scale) * viewport->size.width - EPSILON));
|
|
area.height = MAX (1, ceilf (graphene_vec2_get_y (scale) * viewport->size.height - EPSILON));
|
|
|
|
depth = gdk_memory_depth_merge (gdk_color_state_get_depth (ccs),
|
|
gsk_render_node_get_preferred_depth (node));
|
|
|
|
image = gsk_gpu_device_create_offscreen_image (gsk_gpu_frame_get_device (frame),
|
|
FALSE,
|
|
gdk_memory_depth_get_format (depth),
|
|
gdk_memory_depth_is_srgb (depth),
|
|
area.width, area.height);
|
|
if (image == NULL)
|
|
return NULL;
|
|
|
|
gsk_gpu_node_processor_process (frame,
|
|
image,
|
|
ccs,
|
|
cairo_region_create_rectangle (&area),
|
|
node,
|
|
viewport,
|
|
GSK_RENDER_PASS_OFFSCREEN);
|
|
|
|
return image;
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_get_node_as_image_via_offscreen (GskGpuFrame *frame,
|
|
GskGpuAsImageFlags flags,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
GskGpuImage *result;
|
|
|
|
GSK_DEBUG (FALLBACK, "Offscreening node '%s'", g_type_name_from_instance ((GTypeInstance *) node));
|
|
result = gsk_gpu_node_processor_create_offscreen (frame,
|
|
ccs,
|
|
scale,
|
|
clip_bounds,
|
|
node);
|
|
|
|
*out_bounds = *clip_bounds;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* gsk_gpu_node_copy_image:
|
|
* @frame: The frame the image will be copied in
|
|
* @ccs: color state the copy will be in
|
|
* @image: (transfer full): The image to copy
|
|
* @prepare_mipmap: If the copied image should reserve space for
|
|
* mipmaps
|
|
*
|
|
* Generates a copy of @image, but makes the copy premultiplied and potentially
|
|
* reserves space for mipmaps.
|
|
*
|
|
* Returns: (transfer full): The copy of the image.
|
|
**/
|
|
static GskGpuImage *
|
|
gsk_gpu_copy_image (GskGpuFrame *frame,
|
|
GdkColorState *ccs,
|
|
GskGpuImage *image,
|
|
GdkColorState *image_cs,
|
|
gboolean prepare_mipmap)
|
|
{
|
|
GskGpuImage *copy;
|
|
gsize width, height;
|
|
GskGpuImageFlags flags;
|
|
GdkMemoryDepth depth;
|
|
|
|
width = gsk_gpu_image_get_width (image);
|
|
height = gsk_gpu_image_get_height (image);
|
|
flags = gsk_gpu_image_get_flags (image);
|
|
depth = gdk_memory_format_get_depth (gsk_gpu_image_get_format (image),
|
|
flags & GSK_GPU_IMAGE_SRGB);
|
|
depth = gdk_memory_depth_merge (depth, gdk_color_state_get_depth (ccs));
|
|
|
|
copy = gsk_gpu_device_create_offscreen_image (gsk_gpu_frame_get_device (frame),
|
|
prepare_mipmap,
|
|
gdk_memory_depth_get_format (depth),
|
|
gdk_memory_depth_is_srgb (depth),
|
|
width, height);
|
|
|
|
if (gsk_gpu_frame_should_optimize (frame, GSK_GPU_OPTIMIZE_BLIT) &&
|
|
(flags & (GSK_GPU_IMAGE_NO_BLIT | GSK_GPU_IMAGE_STRAIGHT_ALPHA | GSK_GPU_IMAGE_FILTERABLE)) == GSK_GPU_IMAGE_FILTERABLE &&
|
|
gdk_color_state_equal (ccs, image_cs))
|
|
{
|
|
gsk_gpu_blit_op (frame,
|
|
image,
|
|
copy,
|
|
&(cairo_rectangle_int_t) { 0, 0, width, height },
|
|
&(cairo_rectangle_int_t) { 0, 0, width, height },
|
|
GSK_GPU_BLIT_NEAREST);
|
|
}
|
|
else
|
|
{
|
|
GskGpuNodeProcessor other;
|
|
graphene_rect_t rect = GRAPHENE_RECT_INIT (0, 0, width, height);
|
|
|
|
gsk_gpu_node_processor_init (&other,
|
|
frame,
|
|
copy,
|
|
ccs,
|
|
&(cairo_rectangle_int_t) { 0, 0, width, height },
|
|
&rect);
|
|
|
|
gsk_gpu_render_pass_begin_op (other.frame,
|
|
copy,
|
|
&(cairo_rectangle_int_t) { 0, 0, width, height },
|
|
GSK_GPU_LOAD_OP_DONT_CARE,
|
|
NULL,
|
|
GSK_RENDER_PASS_OFFSCREEN);
|
|
|
|
other.blend = GSK_GPU_BLEND_NONE;
|
|
other.pending_globals |= GSK_GPU_GLOBAL_BLEND;
|
|
gsk_gpu_node_processor_sync_globals (&other, 0);
|
|
|
|
gsk_gpu_node_processor_image_op (&other,
|
|
image,
|
|
image_cs,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&rect,
|
|
&rect);
|
|
|
|
gsk_gpu_render_pass_end_op (other.frame,
|
|
copy,
|
|
GSK_RENDER_PASS_OFFSCREEN);
|
|
|
|
gsk_gpu_node_processor_finish (&other);
|
|
}
|
|
|
|
g_object_unref (image);
|
|
|
|
return copy;
|
|
}
|
|
|
|
/*
|
|
* gsk_gpu_node_processor_get_node_as_image:
|
|
* @self: a node processor
|
|
* @flags: flags for the image
|
|
* @clip_bounds: (nullable): clip rectangle to use or NULL to use
|
|
* the current clip
|
|
* @node: the node to turn into an image
|
|
* @out_bounds: bounds of the the image in node space
|
|
*
|
|
* Generates an image for the given node. The image is restricted to the
|
|
* region in the clip bounds.
|
|
*
|
|
* The resulting image is guaranteed to be premultiplied.
|
|
*
|
|
* Returns: (nullable): The node as an image or %NULL if the node is fully
|
|
* clipped
|
|
**/
|
|
static GskGpuImage *
|
|
gsk_gpu_node_processor_get_node_as_image (GskGpuNodeProcessor *self,
|
|
GskGpuAsImageFlags flags,
|
|
const graphene_rect_t *clip_bounds,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
graphene_rect_t clip;
|
|
|
|
if (clip_bounds == NULL)
|
|
{
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip))
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
if (!gsk_rect_intersection (clip_bounds, &node->bounds, &clip))
|
|
return NULL;
|
|
}
|
|
gsk_rect_snap_to_grid (&clip, &self->scale, &self->offset, &clip);
|
|
|
|
return gsk_gpu_get_node_as_image (self->frame,
|
|
flags,
|
|
self->ccs,
|
|
&clip,
|
|
&self->scale,
|
|
node,
|
|
out_bounds);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_blur_op (GskGpuNodeProcessor *self,
|
|
const graphene_rect_t *rect,
|
|
const graphene_point_t *shadow_offset,
|
|
float blur_radius,
|
|
const GdkColor *shadow_color,
|
|
GskGpuImage *source_image,
|
|
GdkMemoryDepth source_depth,
|
|
const graphene_rect_t *source_rect)
|
|
{
|
|
GskGpuNodeProcessor other;
|
|
GskGpuImage *intermediate;
|
|
graphene_vec2_t direction;
|
|
graphene_rect_t clip_rect, intermediate_rect;
|
|
graphene_point_t real_offset;
|
|
float clip_radius;
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (blur_radius / 2.0);
|
|
|
|
/* FIXME: Handle clip radius growing the clip too much */
|
|
gsk_gpu_node_processor_get_clip_bounds (self, &clip_rect);
|
|
clip_rect.origin.x -= shadow_offset->x;
|
|
clip_rect.origin.y -= shadow_offset->y;
|
|
graphene_rect_inset (&clip_rect, 0.f, -clip_radius);
|
|
if (!gsk_rect_intersection (rect, &clip_rect, &intermediate_rect))
|
|
return;
|
|
|
|
gsk_rect_snap_to_grid (&intermediate_rect, &self->scale, &self->offset, &intermediate_rect);
|
|
|
|
intermediate = gsk_gpu_node_processor_init_draw (&other,
|
|
self->frame,
|
|
self->ccs,
|
|
source_depth,
|
|
&self->scale,
|
|
&intermediate_rect);
|
|
|
|
gsk_gpu_node_processor_sync_globals (&other, 0);
|
|
|
|
graphene_vec2_init (&direction, blur_radius, 0.0f);
|
|
gsk_gpu_blur_op (other.frame,
|
|
gsk_gpu_clip_get_shader_clip (&other.clip, &other.offset, &intermediate_rect),
|
|
other.ccs,
|
|
1,
|
|
&other.offset,
|
|
&(GskGpuShaderImage) {
|
|
source_image,
|
|
GSK_GPU_SAMPLER_TRANSPARENT,
|
|
&intermediate_rect,
|
|
source_rect
|
|
},
|
|
&direction);
|
|
|
|
gsk_gpu_node_processor_finish_draw (&other, intermediate);
|
|
|
|
real_offset = GRAPHENE_POINT_INIT (self->offset.x + shadow_offset->x,
|
|
self->offset.y + shadow_offset->y);
|
|
graphene_vec2_init (&direction, 0.0f, blur_radius);
|
|
if (shadow_color)
|
|
{
|
|
gsk_gpu_blur_shadow_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &real_offset, rect),
|
|
self->ccs,
|
|
1,
|
|
&real_offset,
|
|
&(GskGpuShaderImage) {
|
|
intermediate,
|
|
GSK_GPU_SAMPLER_TRANSPARENT,
|
|
rect,
|
|
&intermediate_rect,
|
|
},
|
|
&direction,
|
|
shadow_color);
|
|
}
|
|
else
|
|
{
|
|
gsk_gpu_blur_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &real_offset, rect),
|
|
self->ccs,
|
|
1,
|
|
&real_offset,
|
|
&(GskGpuShaderImage) {
|
|
intermediate,
|
|
GSK_GPU_SAMPLER_TRANSPARENT,
|
|
rect,
|
|
&intermediate_rect,
|
|
},
|
|
&direction);
|
|
}
|
|
|
|
g_object_unref (intermediate);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_cairo_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuImage *image;
|
|
graphene_rect_t clipped_bounds;
|
|
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clipped_bounds))
|
|
return;
|
|
|
|
gsk_rect_snap_to_grid (&clipped_bounds, &self->scale, &self->offset, &clipped_bounds);
|
|
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
|
|
image = gsk_gpu_upload_cairo_op (self->frame,
|
|
&self->scale,
|
|
&clipped_bounds,
|
|
(GskGpuCairoFunc) gsk_render_node_draw_fallback,
|
|
gsk_render_node_ref (node),
|
|
(GDestroyNotify) gsk_render_node_unref);
|
|
|
|
gsk_gpu_node_processor_image_op (self,
|
|
image,
|
|
GDK_COLOR_STATE_SRGB,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&clipped_bounds);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_without_opacity (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuImage *image;
|
|
graphene_rect_t tex_rect;
|
|
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
|
|
image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
NULL,
|
|
node,
|
|
&tex_rect);
|
|
if (image == NULL)
|
|
return;
|
|
|
|
gsk_gpu_node_processor_image_op (self,
|
|
image,
|
|
self->ccs,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&tex_rect);
|
|
|
|
g_object_unref (image);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_node_clipped (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node,
|
|
const graphene_rect_t *clip_bounds)
|
|
{
|
|
GskGpuClip old_clip;
|
|
graphene_rect_t clip;
|
|
cairo_rectangle_int_t scissor;
|
|
|
|
if (gsk_rect_contains_rect (clip_bounds, &node->bounds))
|
|
{
|
|
gsk_gpu_node_processor_add_node (self, node);
|
|
return;
|
|
}
|
|
|
|
gsk_rect_init_offset (&clip, clip_bounds, &self->offset);
|
|
|
|
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
|
|
|
|
/* Check if we can use scissoring for the clip */
|
|
if (gsk_gpu_node_processor_rect_is_integer (self, &clip, &scissor))
|
|
{
|
|
cairo_rectangle_int_t old_scissor;
|
|
|
|
if (!gdk_rectangle_intersect (&scissor, &self->scissor, &scissor))
|
|
return;
|
|
|
|
old_scissor = self->scissor;
|
|
|
|
if (gsk_gpu_clip_intersect_rect (&self->clip, &old_clip, &clip))
|
|
{
|
|
if (self->clip.type == GSK_GPU_CLIP_ALL_CLIPPED)
|
|
{
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
return;
|
|
}
|
|
else if ((self->clip.type == GSK_GPU_CLIP_RECT || self->clip.type == GSK_GPU_CLIP_CONTAINED) &&
|
|
gsk_rect_contains_rect (&self->clip.rect.bounds, &clip))
|
|
{
|
|
self->clip.type = GSK_GPU_CLIP_NONE;
|
|
}
|
|
|
|
self->scissor = scissor;
|
|
self->pending_globals |= GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_CLIP;
|
|
|
|
gsk_gpu_node_processor_add_node (self, node);
|
|
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
self->scissor = old_scissor;
|
|
self->pending_globals |= GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_CLIP;
|
|
}
|
|
else
|
|
{
|
|
self->scissor = scissor;
|
|
self->pending_globals |= GSK_GPU_GLOBAL_SCISSOR;
|
|
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
|
|
gsk_gpu_node_processor_add_node (self, node);
|
|
|
|
self->scissor = old_scissor;
|
|
self->pending_globals |= GSK_GPU_GLOBAL_SCISSOR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
graphene_rect_t scissored_clip;
|
|
|
|
if (gsk_gpu_node_processor_rect_device_to_clip (self,
|
|
&GSK_RECT_INIT_CAIRO (&self->scissor),
|
|
&scissored_clip))
|
|
{
|
|
if (!gsk_rect_intersection (&scissored_clip, &clip, &clip))
|
|
{
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!gsk_gpu_clip_intersect_rect (&self->clip, &old_clip, &clip))
|
|
{
|
|
GskGpuImage *image;
|
|
graphene_rect_t bounds, tex_rect;
|
|
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
|
|
if (gsk_gpu_node_processor_clip_node_bounds (self, node, &bounds) &&
|
|
gsk_rect_intersection (&bounds, clip_bounds, &bounds))
|
|
image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
&bounds,
|
|
node,
|
|
&tex_rect);
|
|
else
|
|
image = NULL;
|
|
if (image)
|
|
{
|
|
gsk_gpu_node_processor_image_op (self,
|
|
image,
|
|
self->ccs,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&bounds,
|
|
&tex_rect);
|
|
g_object_unref (image);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (self->clip.type == GSK_GPU_CLIP_ALL_CLIPPED)
|
|
{
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
return;
|
|
}
|
|
|
|
self->pending_globals |= GSK_GPU_GLOBAL_CLIP;
|
|
|
|
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));
|
|
}
|
|
|
|
/*
|
|
* gsk_gpu_first_node_begin_rendering:
|
|
* @self: The node processor
|
|
* @info: The first node info
|
|
* @clear_color: (nullable): The color to clear to
|
|
*
|
|
* Begins rendering the given scissor rect and if given clears the background
|
|
* to the given clear color.
|
|
**/
|
|
static void
|
|
gsk_gpu_first_node_begin_rendering (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
float clear_color[4])
|
|
{
|
|
if (info->has_started_rendering)
|
|
{
|
|
if (clear_color &&
|
|
(!info->has_background ||
|
|
(info->has_background &&
|
|
(clear_color[0] != info->background_color[0] ||
|
|
clear_color[1] != info->background_color[1] ||
|
|
clear_color[2] != info->background_color[2] ||
|
|
clear_color[3] != info->background_color[3]))))
|
|
{
|
|
gsk_gpu_clear_op (self->frame, &self->scissor, clear_color);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GskGpuLoadOp load_op;
|
|
|
|
if (!info->whole_area)
|
|
{
|
|
info->has_background = FALSE;
|
|
load_op = GSK_GPU_LOAD_OP_LOAD;
|
|
}
|
|
else if (clear_color)
|
|
{
|
|
info->background_color[0] = clear_color[0];
|
|
info->background_color[1] = clear_color[1];
|
|
info->background_color[2] = clear_color[2];
|
|
info->background_color[3] = clear_color[3];
|
|
info->has_background = TRUE;
|
|
load_op = GSK_GPU_LOAD_OP_CLEAR;
|
|
}
|
|
else
|
|
{
|
|
info->has_background = FALSE;
|
|
load_op = GSK_GPU_LOAD_OP_DONT_CARE;
|
|
}
|
|
|
|
info->has_started_rendering = TRUE;
|
|
gsk_gpu_render_pass_begin_op (self->frame,
|
|
info->target,
|
|
&info->extents,
|
|
load_op,
|
|
clear_color,
|
|
info->pass_type);
|
|
|
|
if (!info->whole_area && clear_color)
|
|
gsk_gpu_clear_op (self->frame, &self->scissor, clear_color);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* gsk_gpu_node_processor_clip_first_node:
|
|
* @self: the nodeprocessor
|
|
* @info: the first pass info
|
|
* @opaque: an opaque rectangle to clip to
|
|
*
|
|
* Shrinks the clip during a first node determination to only cover
|
|
* the passed in opaque rect - or rather its intersection with the
|
|
* previous clip.
|
|
*
|
|
* This can fail if the resulting scissor rect would be smaller than
|
|
* min_occlusion_pixels and not warrant an occlusion pass.
|
|
*
|
|
* Adjusts scissor rect and clip, when not starting a first node,
|
|
* you need to revert them.
|
|
*
|
|
* Returns: TRUE if the adjustment was successful.
|
|
**/
|
|
static gboolean
|
|
gsk_gpu_node_processor_clip_first_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
const graphene_rect_t *opaque)
|
|
{
|
|
cairo_rectangle_int_t device_clip;
|
|
graphene_rect_t rect;
|
|
|
|
if (!gsk_gpu_node_processor_rect_to_device_shrink (self, opaque, &device_clip) ||
|
|
!gdk_rectangle_intersect (&device_clip, &self->scissor, &device_clip) ||
|
|
device_clip.width * device_clip.height < info->min_occlusion_pixels)
|
|
return FALSE;
|
|
|
|
self->scissor = device_clip;
|
|
|
|
gsk_gpu_node_processor_rect_device_to_clip (self,
|
|
&GSK_RECT_INIT_CAIRO (&device_clip),
|
|
&rect);
|
|
gsk_gpu_clip_init_empty (&self->clip, &rect);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_node_clipped (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
const graphene_rect_t *clip,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuClip old_clip;
|
|
cairo_rectangle_int_t old_scissor;
|
|
|
|
old_scissor = self->scissor;
|
|
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
|
|
|
|
if (!gsk_gpu_node_processor_clip_first_node (self, info, clip))
|
|
return FALSE;
|
|
|
|
if (gsk_gpu_node_processor_add_first_node (self,
|
|
info,
|
|
node))
|
|
{
|
|
/* don't revert clip here, the add_first_node() adjusted it to a correct value */
|
|
return TRUE;
|
|
}
|
|
|
|
self->scissor = old_scissor;
|
|
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_clip_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node)
|
|
{
|
|
return gsk_gpu_node_processor_add_first_node_clipped (self,
|
|
info,
|
|
&node->bounds,
|
|
gsk_clip_node_get_child (node));
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_rounded_clip_node_with_mask (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuNodeProcessor other;
|
|
graphene_rect_t clip_bounds, child_rect;
|
|
GskGpuImage *child_image, *mask_image;
|
|
GdkColor white;
|
|
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip_bounds))
|
|
return;
|
|
gsk_rect_snap_to_grid (&clip_bounds, &self->scale, &self->offset, &clip_bounds);
|
|
|
|
child_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
&clip_bounds,
|
|
gsk_rounded_clip_node_get_child (node),
|
|
&child_rect);
|
|
if (child_image == NULL)
|
|
return;
|
|
|
|
mask_image = gsk_gpu_node_processor_init_draw (&other,
|
|
self->frame,
|
|
self->ccs,
|
|
gdk_memory_depth_merge (gdk_color_state_get_depth (self->ccs),
|
|
gsk_render_node_get_preferred_depth (node)),
|
|
&self->scale,
|
|
&clip_bounds);
|
|
|
|
gdk_color_init (&white, self->ccs, ((float[]){ 1, 1, 1, 1 }));
|
|
gsk_gpu_node_processor_sync_globals (&other, 0);
|
|
gsk_gpu_rounded_color_op (other.frame,
|
|
gsk_gpu_clip_get_shader_clip (&other.clip, &other.offset, &node->bounds),
|
|
self->ccs,
|
|
1,
|
|
&other.offset,
|
|
gsk_rounded_clip_node_get_clip (node),
|
|
&white);
|
|
gsk_gpu_node_processor_finish_draw (&other, mask_image);
|
|
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
gsk_gpu_mask_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &clip_bounds),
|
|
&clip_bounds,
|
|
&self->offset,
|
|
self->opacity,
|
|
GSK_MASK_MODE_ALPHA,
|
|
&(GskGpuShaderImage) {
|
|
child_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&child_rect,
|
|
},
|
|
&(GskGpuShaderImage) {
|
|
mask_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&clip_bounds,
|
|
});
|
|
|
|
g_object_unref (child_image);
|
|
g_object_unref (mask_image);
|
|
gdk_color_finish (&white);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_rounded_clip_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuClip old_clip;
|
|
GskRoundedRect clip;
|
|
const GskRoundedRect *original_clip;
|
|
GskRenderNode *child;
|
|
graphene_rect_t scissor;
|
|
|
|
child = gsk_rounded_clip_node_get_child (node);
|
|
original_clip = gsk_rounded_clip_node_get_clip (node);
|
|
|
|
/* Common case for entries etc: rounded solid color background.
|
|
* And we have a shader for that */
|
|
if (gsk_render_node_get_node_type (child) == GSK_COLOR_NODE &&
|
|
gsk_rect_contains_rect (&child->bounds, &original_clip->bounds))
|
|
{
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
|
|
gsk_gpu_rounded_color_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &original_clip->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
original_clip,
|
|
gsk_color_node_get_color2 (child));
|
|
return;
|
|
}
|
|
|
|
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
|
|
|
|
clip = *original_clip;
|
|
gsk_rounded_rect_offset (&clip, self->offset.x, self->offset.y);
|
|
|
|
if (!gsk_gpu_clip_intersect_rounded_rect (&self->clip, &old_clip, &clip))
|
|
{
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
gsk_gpu_node_processor_add_rounded_clip_node_with_mask (self, node);
|
|
return;
|
|
}
|
|
|
|
if (gsk_gpu_node_processor_rect_device_to_clip (self,
|
|
&GSK_RECT_INIT_CAIRO (&self->scissor),
|
|
&scissor))
|
|
{
|
|
GskGpuClip scissored_clip;
|
|
if (gsk_gpu_clip_intersect_rect (&scissored_clip, &self->clip, &scissor))
|
|
gsk_gpu_clip_init_copy (&self->clip, &scissored_clip);
|
|
}
|
|
|
|
if (self->clip.type == GSK_GPU_CLIP_ALL_CLIPPED)
|
|
{
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
return;
|
|
}
|
|
|
|
self->pending_globals |= GSK_GPU_GLOBAL_CLIP;
|
|
|
|
gsk_gpu_node_processor_add_node (self, gsk_rounded_clip_node_get_child (node));
|
|
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
self->pending_globals |= GSK_GPU_GLOBAL_CLIP;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_rounded_clip_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node)
|
|
{
|
|
graphene_rect_t cover, clip;
|
|
|
|
gsk_gpu_node_processor_get_clip_bounds (self, &clip);
|
|
gsk_rounded_rect_get_largest_cover (gsk_rounded_clip_node_get_clip (node),
|
|
&clip,
|
|
&cover);
|
|
|
|
return gsk_gpu_node_processor_add_first_node_clipped (self,
|
|
info,
|
|
&cover,
|
|
gsk_rounded_clip_node_get_child (node));
|
|
}
|
|
|
|
static GskTransform *
|
|
gsk_transform_dihedral (GskTransform *transform,
|
|
GdkDihedral dihedral)
|
|
{
|
|
int rotate = dihedral & 3;
|
|
int flip = dihedral & 4;
|
|
|
|
if (flip)
|
|
transform = gsk_transform_scale (transform, -1.0, 1.0);
|
|
|
|
if (rotate)
|
|
transform = gsk_transform_rotate (transform, rotate * 90.0f);
|
|
|
|
return transform;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_transform_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNode *child;
|
|
GskTransform *transform;
|
|
graphene_point_t old_offset;
|
|
graphene_vec2_t old_scale;
|
|
GskTransform *old_modelview;
|
|
GskGpuClip old_clip;
|
|
|
|
child = gsk_transform_node_get_child (node);
|
|
transform = gsk_transform_node_get_transform (node);
|
|
|
|
switch (gsk_transform_get_fine_category (transform))
|
|
{
|
|
case GSK_FINE_TRANSFORM_CATEGORY_IDENTITY:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_TRANSLATE:
|
|
{
|
|
float dx, dy;
|
|
gsk_transform_to_translate (transform, &dx, &dy);
|
|
old_offset = self->offset;
|
|
self->offset.x += dx;
|
|
self->offset.y += dy;
|
|
gsk_gpu_node_processor_add_node (self, child);
|
|
self->offset = old_offset;
|
|
}
|
|
return;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_AFFINE:
|
|
{
|
|
float dx, dy, scale_x, scale_y;
|
|
|
|
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
|
|
old_offset = self->offset;
|
|
old_scale = self->scale;
|
|
old_modelview = self->modelview;
|
|
|
|
gsk_transform_to_affine (transform, &scale_x, &scale_y, &dx, &dy);
|
|
gsk_gpu_clip_scale (&self->clip, &old_clip, GDK_DIHEDRAL_NORMAL, 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, scale_x, scale_y);
|
|
graphene_vec2_multiply (&self->scale, &old_scale, &self->scale);
|
|
}
|
|
break;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL:
|
|
{
|
|
GdkDihedral dihedral, inverted;
|
|
float xx, xy, yx, yy, dx, dy, scale_x, scale_y, old_scale_x, old_scale_y;
|
|
|
|
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
|
|
old_offset = self->offset;
|
|
old_scale = self->scale;
|
|
old_modelview = gsk_transform_ref (self->modelview);
|
|
|
|
gsk_transform_to_dihedral (transform, &dihedral, &scale_x, &scale_y, &dx, &dy);
|
|
inverted = gdk_dihedral_invert (dihedral);
|
|
gdk_dihedral_get_mat2 (inverted, &xx, &xy, &yx, &yy);
|
|
gsk_gpu_clip_scale (&self->clip, &old_clip, inverted, scale_x, scale_y);
|
|
self->offset.x = (self->offset.x + dx) / scale_x;
|
|
self->offset.y = (self->offset.y + dy) / scale_y;
|
|
self->offset = GRAPHENE_POINT_INIT (xx * self->offset.x + xy * self->offset.y,
|
|
yx * self->offset.x + yy * self->offset.y);
|
|
old_scale_x = graphene_vec2_get_x (&old_scale);
|
|
old_scale_y = graphene_vec2_get_y (&old_scale);
|
|
graphene_vec2_init (&self->scale,
|
|
fabs (scale_x * (old_scale_x * xx + old_scale_y * yx)),
|
|
fabs (scale_y * (old_scale_x * xy + old_scale_y * yy)));
|
|
self->modelview = gsk_transform_dihedral (self->modelview, dihedral);
|
|
}
|
|
break;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_UNKNOWN:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_ANY:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_3D:
|
|
{
|
|
GskTransform *clip_transform;
|
|
float scale_x, scale_y, old_pixels, new_pixels;
|
|
graphene_rect_t scissor;
|
|
|
|
clip_transform = gsk_transform_transform (gsk_transform_translate (NULL, &self->offset), transform);
|
|
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
|
|
|
|
if (gsk_gpu_clip_contains_rect (&self->clip, &self->offset, &node->bounds))
|
|
{
|
|
gsk_gpu_clip_init_contained (&self->clip, &child->bounds);
|
|
}
|
|
else if (old_clip.type == GSK_GPU_CLIP_NONE)
|
|
{
|
|
GskTransform *inverse;
|
|
graphene_rect_t new_bounds;
|
|
inverse = gsk_transform_invert (gsk_transform_ref (clip_transform));
|
|
gsk_transform_transform_bounds (inverse, &old_clip.rect.bounds, &new_bounds);
|
|
gsk_transform_unref (inverse);
|
|
gsk_gpu_clip_init_empty (&self->clip, &new_bounds);
|
|
}
|
|
else if (!gsk_gpu_clip_transform (&self->clip, &old_clip, clip_transform, &child->bounds))
|
|
{
|
|
GskGpuImage *image;
|
|
graphene_rect_t tex_rect;
|
|
gsk_transform_unref (clip_transform);
|
|
/* This cannot loop because the next time we'll hit the branch above */
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
NULL,
|
|
node,
|
|
&tex_rect);
|
|
if (image != NULL)
|
|
{
|
|
gsk_gpu_node_processor_image_op (self,
|
|
image,
|
|
self->ccs,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&tex_rect);
|
|
g_object_unref (image);
|
|
}
|
|
return;
|
|
}
|
|
|
|
old_offset = self->offset;
|
|
old_scale = self->scale;
|
|
old_modelview = gsk_transform_ref (self->modelview);
|
|
|
|
self->modelview = gsk_transform_scale (self->modelview,
|
|
graphene_vec2_get_x (&self->scale),
|
|
graphene_vec2_get_y (&self->scale));
|
|
self->modelview = gsk_transform_transform (self->modelview, clip_transform);
|
|
gsk_transform_unref (clip_transform);
|
|
|
|
extract_scale_from_transform (self->modelview, &scale_x, &scale_y);
|
|
|
|
old_pixels = MAX (graphene_vec2_get_x (&old_scale) * old_clip.rect.bounds.size.width,
|
|
graphene_vec2_get_y (&old_scale) * old_clip.rect.bounds.size.height);
|
|
new_pixels = MAX (scale_x * self->clip.rect.bounds.size.width,
|
|
scale_y * self->clip.rect.bounds.size.height);
|
|
|
|
/* Check that our offscreen doesn't get too big. 1.5 ~ sqrt(2) */
|
|
if (new_pixels > 1.5 * old_pixels)
|
|
{
|
|
float forced_downscale = 2 * old_pixels / new_pixels;
|
|
scale_x *= forced_downscale;
|
|
scale_y *= forced_downscale;
|
|
}
|
|
|
|
self->modelview = gsk_transform_scale (self->modelview, 1 / scale_x, 1 / scale_y);
|
|
graphene_vec2_init (&self->scale, scale_x, scale_y);
|
|
self->offset = *graphene_point_zero ();
|
|
|
|
if (gsk_gpu_node_processor_rect_device_to_clip (self,
|
|
&GSK_RECT_INIT_CAIRO (&self->scissor),
|
|
&scissor))
|
|
{
|
|
GskGpuClip scissored_clip;
|
|
if (gsk_gpu_clip_intersect_rect (&scissored_clip, &self->clip, &scissor))
|
|
gsk_gpu_clip_init_copy (&self->clip, &scissored_clip);
|
|
|
|
if (self->clip.type == GSK_GPU_CLIP_ALL_CLIPPED)
|
|
{
|
|
self->offset = old_offset;
|
|
self->scale = old_scale;
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
}
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
self->pending_globals |= GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP;
|
|
if (self->modelview != old_modelview)
|
|
self->pending_globals |= GSK_GPU_GLOBAL_MATRIX;
|
|
|
|
gsk_gpu_node_processor_add_node (self, child);
|
|
|
|
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;
|
|
if (self->modelview != old_modelview)
|
|
{
|
|
self->pending_globals |= GSK_GPU_GLOBAL_MATRIX;
|
|
gsk_transform_unref (self->modelview);
|
|
self->modelview = old_modelview;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_transform_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
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_fine_category (transform))
|
|
{
|
|
case GSK_FINE_TRANSFORM_CATEGORY_IDENTITY:
|
|
case GSK_FINE_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,
|
|
info,
|
|
gsk_transform_node_get_child (node));
|
|
self->offset = old_offset;
|
|
return result;
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_AFFINE:
|
|
gsk_transform_to_affine (transform, &scale_x, &scale_y, &dx, &dy);
|
|
|
|
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, GDK_DIHEDRAL_NORMAL, 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,
|
|
info,
|
|
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_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL:
|
|
{
|
|
GdkDihedral dihedral, inverted;
|
|
float xx, xy, yx, yy, old_scale_x, old_scale_y;
|
|
GskTransform *old_modelview;
|
|
|
|
gsk_gpu_clip_init_copy (&old_clip, &self->clip);
|
|
old_offset = self->offset;
|
|
old_scale = self->scale;
|
|
old_modelview = gsk_transform_ref (self->modelview);
|
|
|
|
gsk_transform_to_dihedral (transform, &dihedral, &scale_x, &scale_y, &dx, &dy);
|
|
inverted = gdk_dihedral_invert (dihedral);
|
|
gdk_dihedral_get_mat2 (inverted, &xx, &xy, &yx, &yy);
|
|
gsk_gpu_clip_scale (&self->clip, &old_clip, inverted, scale_x, scale_y);
|
|
self->offset.x = (self->offset.x + dx) / scale_x;
|
|
self->offset.y = (self->offset.y + dy) / scale_y;
|
|
self->offset = GRAPHENE_POINT_INIT (xx * self->offset.x + xy * self->offset.y,
|
|
yx * self->offset.x + yy * self->offset.y);
|
|
old_scale_x = graphene_vec2_get_x (&old_scale);
|
|
old_scale_y = graphene_vec2_get_y (&old_scale);
|
|
graphene_vec2_init (&self->scale,
|
|
fabs (scale_x * (old_scale_x * xx + old_scale_y * yx)),
|
|
fabs (scale_y * (old_scale_x * xy + old_scale_y * yy)));
|
|
self->modelview = gsk_transform_dihedral (self->modelview, dihedral);
|
|
|
|
self->pending_globals |= GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP;
|
|
|
|
result = gsk_gpu_node_processor_add_first_node (self,
|
|
info,
|
|
gsk_transform_node_get_child (node));
|
|
|
|
self->offset = old_offset;
|
|
self->scale = old_scale;
|
|
gsk_gpu_clip_init_copy (&self->clip, &old_clip);
|
|
gsk_transform_unref (self->modelview);
|
|
self->modelview = old_modelview;
|
|
|
|
self->pending_globals |= GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP;
|
|
|
|
return result;
|
|
}
|
|
|
|
case GSK_FINE_TRANSFORM_CATEGORY_2D:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_UNKNOWN:
|
|
case GSK_FINE_TRANSFORM_CATEGORY_ANY:
|
|
case GSK_FINE_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)
|
|
{
|
|
float old_opacity = self->opacity;
|
|
|
|
self->opacity *= gsk_opacity_node_get_opacity (node);
|
|
|
|
gsk_gpu_node_processor_add_node (self, gsk_opacity_node_get_child (node));
|
|
|
|
self->opacity = old_opacity;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_color_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
cairo_rectangle_int_t int_clipped;
|
|
graphene_rect_t rect, clipped;
|
|
float clear_color[4];
|
|
|
|
gsk_rect_init_offset (&rect, &node->bounds, &self->offset);
|
|
gsk_rect_intersection (&self->clip.rect.bounds, &rect, &clipped);
|
|
|
|
if (gsk_gpu_frame_should_optimize (self->frame, GSK_GPU_OPTIMIZE_CLEAR) &&
|
|
gdk_color_is_opaque (gsk_color_node_get_color2 (node)) &&
|
|
self->opacity >= 1.0 &&
|
|
node->bounds.size.width * node->bounds.size.height > 100 * 100 && /* not worth the effort for small images */
|
|
gsk_gpu_node_processor_rect_is_integer (self, &clipped, &int_clipped))
|
|
{
|
|
/* now handle all the clip */
|
|
if (!gdk_rectangle_intersect (&int_clipped, &self->scissor, &int_clipped))
|
|
return;
|
|
|
|
/* we have handled the bounds, now do the corners */
|
|
if (self->clip.type == GSK_GPU_CLIP_ROUNDED)
|
|
{
|
|
graphene_rect_t cover;
|
|
GskGpuShaderClip shader_clip;
|
|
float scale_x, scale_y;
|
|
|
|
if (self->modelview)
|
|
{
|
|
/* Yuck, rounded clip and modelview. I give up. */
|
|
gsk_gpu_color_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
&node->bounds,
|
|
gsk_color_node_get_color2 (node));
|
|
return;
|
|
}
|
|
|
|
scale_x = graphene_vec2_get_x (&self->scale);
|
|
scale_y = graphene_vec2_get_y (&self->scale);
|
|
clipped = GRAPHENE_RECT_INIT (int_clipped.x / scale_x, int_clipped.y / scale_y,
|
|
int_clipped.width / scale_x, int_clipped.height / scale_y);
|
|
shader_clip = gsk_gpu_clip_get_shader_clip (&self->clip, graphene_point_zero(), &clipped);
|
|
if (shader_clip != GSK_GPU_SHADER_CLIP_NONE)
|
|
{
|
|
gsk_rounded_rect_get_largest_cover (&self->clip.rect, &clipped, &cover);
|
|
int_clipped.x = ceilf (cover.origin.x * scale_x);
|
|
int_clipped.y = ceilf (cover.origin.y * scale_y);
|
|
int_clipped.width = floorf ((cover.origin.x + cover.size.width) * scale_x) - int_clipped.x;
|
|
int_clipped.height = floorf ((cover.origin.y + cover.size.height) * scale_y) - int_clipped.y;
|
|
if (int_clipped.width == 0 || int_clipped.height == 0)
|
|
{
|
|
gsk_gpu_color_op (self->frame,
|
|
shader_clip,
|
|
self->ccs,
|
|
self->opacity,
|
|
graphene_point_zero (),
|
|
&clipped,
|
|
gsk_color_node_get_color2 (node));
|
|
return;
|
|
}
|
|
cover = GRAPHENE_RECT_INIT (int_clipped.x / scale_x, int_clipped.y / scale_y,
|
|
int_clipped.width / scale_x, int_clipped.height / scale_y);
|
|
if (clipped.origin.x != cover.origin.x)
|
|
gsk_gpu_color_op (self->frame,
|
|
shader_clip,
|
|
self->ccs,
|
|
self->opacity,
|
|
graphene_point_zero (),
|
|
&GRAPHENE_RECT_INIT (clipped.origin.x, clipped.origin.y, cover.origin.x - clipped.origin.x, clipped.size.height),
|
|
gsk_color_node_get_color2 (node));
|
|
if (clipped.origin.y != cover.origin.y)
|
|
gsk_gpu_color_op (self->frame,
|
|
shader_clip,
|
|
self->ccs,
|
|
self->opacity,
|
|
graphene_point_zero (),
|
|
&GRAPHENE_RECT_INIT (clipped.origin.x, clipped.origin.y, clipped.size.width, cover.origin.y - clipped.origin.y),
|
|
gsk_color_node_get_color2 (node));
|
|
if (clipped.origin.x + clipped.size.width != cover.origin.x + cover.size.width)
|
|
gsk_gpu_color_op (self->frame,
|
|
shader_clip,
|
|
self->ccs,
|
|
self->opacity,
|
|
graphene_point_zero (),
|
|
&GRAPHENE_RECT_INIT (cover.origin.x + cover.size.width,
|
|
clipped.origin.y,
|
|
clipped.origin.x + clipped.size.width - cover.origin.x - cover.size.width,
|
|
clipped.size.height),
|
|
gsk_color_node_get_color2 (node));
|
|
if (clipped.origin.y + clipped.size.height != cover.origin.y + cover.size.height)
|
|
gsk_gpu_color_op (self->frame,
|
|
shader_clip,
|
|
self->ccs,
|
|
self->opacity,
|
|
graphene_point_zero (),
|
|
&GRAPHENE_RECT_INIT (clipped.origin.x,
|
|
cover.origin.y + cover.size.height,
|
|
clipped.size.width,
|
|
clipped.origin.y + clipped.size.height - cover.origin.y - cover.size.height),
|
|
gsk_color_node_get_color2 (node));
|
|
}
|
|
}
|
|
|
|
gdk_color_to_float (gsk_color_node_get_color2 (node), self->ccs, clear_color);
|
|
gsk_gpu_clear_op (self->frame, &int_clipped, clear_color);
|
|
return;
|
|
}
|
|
|
|
gsk_gpu_color_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
&node->bounds,
|
|
gsk_color_node_get_color2 (node));
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_color_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node)
|
|
{
|
|
float clear_color[4];
|
|
|
|
if (!node->fully_opaque)
|
|
return FALSE;
|
|
|
|
if (!gsk_gpu_node_processor_clip_first_node (self, info, &node->bounds))
|
|
return FALSE;
|
|
|
|
gdk_color_to_float (gsk_color_node_get_color2 (node), self->ccs, clear_color);
|
|
gsk_gpu_first_node_begin_rendering (self, info, clear_color);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_border_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
gsk_gpu_border_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
gsk_border_node_get_outline (node),
|
|
graphene_point_zero (),
|
|
gsk_border_node_get_widths (node),
|
|
gsk_border_node_get_colors2 (node));
|
|
}
|
|
|
|
static gboolean
|
|
texture_node_should_mipmap (GskRenderNode *node,
|
|
GskGpuFrame *frame,
|
|
const graphene_vec2_t *scale)
|
|
{
|
|
GdkTexture *texture;
|
|
|
|
texture = gsk_texture_node_get_texture (node);
|
|
|
|
if (!gsk_gpu_frame_should_optimize (frame, GSK_GPU_OPTIMIZE_MIPMAP))
|
|
return FALSE;
|
|
|
|
return gdk_texture_get_width (texture) > 2 * node->bounds.size.width * graphene_vec2_get_x (scale) ||
|
|
gdk_texture_get_height (texture) > 2 * node->bounds.size.height * graphene_vec2_get_y (scale);
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_lookup_texture (GskGpuFrame *frame,
|
|
GdkColorState *ccs,
|
|
GdkTexture *texture,
|
|
gboolean try_mipmap,
|
|
GdkColorState **out_image_cs)
|
|
{
|
|
GskGpuCache *cache;
|
|
GdkColorState *image_cs;
|
|
GskGpuImage *image;
|
|
|
|
cache = gsk_gpu_device_get_cache (gsk_gpu_frame_get_device (frame));
|
|
|
|
image = gsk_gpu_cache_lookup_texture_image (cache, texture, ccs);
|
|
if (image)
|
|
{
|
|
*out_image_cs = ccs;
|
|
return image;
|
|
}
|
|
|
|
image = gsk_gpu_cache_lookup_texture_image (cache, texture, NULL);
|
|
if (image == NULL)
|
|
image = gsk_gpu_frame_upload_texture (frame, try_mipmap, texture);
|
|
|
|
/* Happens ie for oversized textures */
|
|
if (image == NULL)
|
|
return NULL;
|
|
|
|
image_cs = gdk_texture_get_color_state (texture);
|
|
|
|
if (gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_SRGB)
|
|
{
|
|
image_cs = gdk_color_state_get_no_srgb_tf (image_cs);
|
|
g_assert (image_cs);
|
|
}
|
|
|
|
*out_image_cs = image_cs;
|
|
return image;
|
|
}
|
|
|
|
static GskGpuSampler
|
|
gsk_gpu_sampler_for_scaling_filter (GskScalingFilter scaling_filter)
|
|
{
|
|
switch (scaling_filter)
|
|
{
|
|
case GSK_SCALING_FILTER_LINEAR:
|
|
return GSK_GPU_SAMPLER_DEFAULT;
|
|
|
|
case GSK_SCALING_FILTER_NEAREST:
|
|
return GSK_GPU_SAMPLER_NEAREST;
|
|
|
|
case GSK_SCALING_FILTER_TRILINEAR:
|
|
return GSK_GPU_SAMPLER_MIPMAP_DEFAULT;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
return GSK_GPU_SAMPLER_DEFAULT;
|
|
}
|
|
}
|
|
|
|
/* must be set up with BLEND_ADD to avoid seams */
|
|
static void
|
|
gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self,
|
|
const graphene_rect_t *texture_bounds,
|
|
GdkTexture *texture,
|
|
GskScalingFilter scaling_filter)
|
|
{
|
|
GskGpuCache *cache;
|
|
GskGpuDevice *device;
|
|
GskGpuImage *tile;
|
|
GdkColorState *tile_cs;
|
|
GskGpuSampler sampler;
|
|
gboolean need_mipmap;
|
|
GdkMemoryTexture *memtex;
|
|
GdkTexture *subtex;
|
|
float scale_factor, scaled_tile_width, scaled_tile_height;
|
|
gsize tile_size, width, height, n_width, n_height, x, y;
|
|
graphene_rect_t clip_bounds;
|
|
guint lod_level;
|
|
|
|
device = gsk_gpu_frame_get_device (self->frame);
|
|
cache = gsk_gpu_device_get_cache (device);
|
|
sampler = gsk_gpu_sampler_for_scaling_filter (scaling_filter);
|
|
need_mipmap = scaling_filter == GSK_SCALING_FILTER_TRILINEAR;
|
|
gsk_gpu_node_processor_get_clip_bounds (self, &clip_bounds);
|
|
width = gdk_texture_get_width (texture);
|
|
height = gdk_texture_get_height (texture);
|
|
tile_size = gsk_gpu_device_get_tile_size (device);
|
|
scale_factor = MIN (width / MAX (tile_size, texture_bounds->size.width),
|
|
height / MAX (tile_size, texture_bounds->size.height));
|
|
if (scale_factor <= 1.0)
|
|
lod_level = 0;
|
|
else
|
|
lod_level = floor (log2f (scale_factor));
|
|
tile_size <<= lod_level;
|
|
n_width = (width + tile_size - 1) / tile_size;
|
|
n_height = (height + tile_size - 1) / tile_size;
|
|
scaled_tile_width = texture_bounds->size.width * tile_size / width;
|
|
scaled_tile_height = texture_bounds->size.height * tile_size / height;
|
|
|
|
memtex = NULL;
|
|
for (y = 0; y < n_height; y++)
|
|
{
|
|
for (x = 0; x < n_width; x++)
|
|
{
|
|
graphene_rect_t tile_rect = GRAPHENE_RECT_INIT (texture_bounds->origin.x + scaled_tile_width * x,
|
|
texture_bounds->origin.y + scaled_tile_height * y,
|
|
scaled_tile_width,
|
|
scaled_tile_height);
|
|
if (!gsk_rect_intersection (&tile_rect, texture_bounds, &tile_rect) ||
|
|
!gsk_rect_intersects (&clip_bounds, &tile_rect))
|
|
continue;
|
|
|
|
tile = gsk_gpu_cache_lookup_tile (cache, texture, lod_level, scaling_filter, y * n_width + x, &tile_cs);
|
|
|
|
if (tile == NULL)
|
|
{
|
|
if (memtex == NULL)
|
|
memtex = gdk_memory_texture_from_texture (texture);
|
|
subtex = gdk_memory_texture_new_subtexture (memtex,
|
|
x * tile_size,
|
|
y * tile_size,
|
|
MIN (tile_size, width - x * tile_size),
|
|
MIN (tile_size, height - y * tile_size));
|
|
tile = gsk_gpu_upload_texture_op_try (self->frame, need_mipmap, lod_level, scaling_filter, subtex);
|
|
g_object_unref (subtex);
|
|
if (tile == NULL)
|
|
{
|
|
g_warning ("failed to create %zux%zu tile for %zux%zu texture. Out of memory?",
|
|
tile_size, tile_size, width, height);
|
|
goto out;
|
|
}
|
|
|
|
tile_cs = gdk_texture_get_color_state (texture);
|
|
if (gsk_gpu_image_get_flags (tile) & GSK_GPU_IMAGE_SRGB)
|
|
{
|
|
tile_cs = gdk_color_state_get_no_srgb_tf (tile_cs);
|
|
g_assert (tile_cs);
|
|
}
|
|
|
|
gsk_gpu_cache_cache_tile (cache, texture, lod_level, scaling_filter, y * n_width + x, tile, tile_cs);
|
|
}
|
|
|
|
if (need_mipmap &&
|
|
(gsk_gpu_image_get_flags (tile) & (GSK_GPU_IMAGE_STRAIGHT_ALPHA | GSK_GPU_IMAGE_CAN_MIPMAP)) != GSK_GPU_IMAGE_CAN_MIPMAP)
|
|
{
|
|
tile = gsk_gpu_copy_image (self->frame, self->ccs, tile, tile_cs, TRUE);
|
|
tile_cs = self->ccs;
|
|
gsk_gpu_cache_cache_tile (cache, texture, lod_level, scaling_filter, y * n_width + x, tile, tile_cs);
|
|
}
|
|
if (need_mipmap && !(gsk_gpu_image_get_flags (tile) & GSK_GPU_IMAGE_MIPMAP))
|
|
gsk_gpu_mipmap_op (self->frame, tile);
|
|
|
|
gsk_gpu_node_processor_image_op (self,
|
|
tile,
|
|
tile_cs,
|
|
sampler,
|
|
&tile_rect,
|
|
&tile_rect);
|
|
|
|
g_object_unref (tile);
|
|
}
|
|
}
|
|
|
|
out:
|
|
g_clear_object (&memtex);
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
const graphene_rect_t *texture_bounds,
|
|
GdkTexture *texture,
|
|
GskScalingFilter scaling_filter)
|
|
{
|
|
GskGpuNodeProcessor self;
|
|
GskGpuImage *image;
|
|
|
|
image = gsk_gpu_node_processor_init_draw (&self,
|
|
frame,
|
|
ccs,
|
|
gdk_texture_get_depth (texture),
|
|
scale,
|
|
clip_bounds);
|
|
if (image == NULL)
|
|
return NULL;
|
|
|
|
self.blend = GSK_GPU_BLEND_ADD;
|
|
self.pending_globals |= GSK_GPU_GLOBAL_BLEND;
|
|
gsk_gpu_node_processor_sync_globals (&self, 0);
|
|
|
|
gsk_gpu_node_processor_draw_texture_tiles (&self,
|
|
texture_bounds,
|
|
texture,
|
|
scaling_filter);
|
|
|
|
gsk_gpu_node_processor_finish_draw (&self, image);
|
|
|
|
return image;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_texture_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GdkColorState *image_cs;
|
|
GskGpuImage *image;
|
|
GdkTexture *texture;
|
|
gboolean should_mipmap;
|
|
|
|
texture = gsk_texture_node_get_texture (node);
|
|
should_mipmap = texture_node_should_mipmap (node, self->frame, &self->scale);
|
|
|
|
image = gsk_gpu_lookup_texture (self->frame, self->ccs, texture, should_mipmap, &image_cs);
|
|
|
|
if (image == NULL)
|
|
{
|
|
graphene_rect_t clip, rounded_clip;
|
|
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip))
|
|
return;
|
|
gsk_rect_snap_to_grid (&clip, &self->scale, &self->offset, &rounded_clip);
|
|
|
|
image = gsk_gpu_get_texture_tiles_as_image (self->frame,
|
|
self->ccs,
|
|
&rounded_clip,
|
|
&self->scale,
|
|
&node->bounds,
|
|
texture,
|
|
should_mipmap ? GSK_SCALING_FILTER_TRILINEAR : GSK_SCALING_FILTER_LINEAR);
|
|
gsk_gpu_node_processor_image_op (self,
|
|
image,
|
|
self->ccs,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&clip,
|
|
&rounded_clip);
|
|
g_object_unref (image);
|
|
return;
|
|
}
|
|
|
|
if (should_mipmap)
|
|
{
|
|
if ((gsk_gpu_image_get_flags (image) & (GSK_GPU_IMAGE_STRAIGHT_ALPHA | GSK_GPU_IMAGE_CAN_MIPMAP)) != GSK_GPU_IMAGE_CAN_MIPMAP ||
|
|
!gdk_color_state_equal (image_cs, self->ccs))
|
|
{
|
|
image = gsk_gpu_copy_image (self->frame, self->ccs, image, image_cs, TRUE);
|
|
image_cs = self->ccs;
|
|
gsk_gpu_cache_cache_texture_image (gsk_gpu_device_get_cache (gsk_gpu_frame_get_device (self->frame)),
|
|
texture,
|
|
image,
|
|
image_cs);
|
|
}
|
|
|
|
if (!(gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_MIPMAP))
|
|
gsk_gpu_mipmap_op (self->frame, image);
|
|
|
|
gsk_gpu_node_processor_image_op (self,
|
|
image,
|
|
image_cs,
|
|
GSK_GPU_SAMPLER_MIPMAP_DEFAULT,
|
|
&node->bounds,
|
|
&node->bounds);
|
|
}
|
|
else
|
|
{
|
|
gsk_gpu_node_processor_image_op (self,
|
|
image,
|
|
image_cs,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&node->bounds);
|
|
}
|
|
|
|
g_object_unref (image);
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_get_texture_node_as_image (GskGpuFrame *frame,
|
|
GskGpuAsImageFlags flags,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
GdkTexture *texture = gsk_texture_node_get_texture (node);
|
|
GdkColorState *image_cs;
|
|
GskGpuImage *image;
|
|
gboolean should_mipmap;
|
|
|
|
should_mipmap = texture_node_should_mipmap (node, frame, scale);
|
|
image = gsk_gpu_lookup_texture (frame, ccs, texture, FALSE, &image_cs);
|
|
|
|
if (image == NULL)
|
|
{
|
|
image = gsk_gpu_get_texture_tiles_as_image (frame,
|
|
ccs,
|
|
clip_bounds,
|
|
scale,
|
|
&node->bounds,
|
|
gsk_texture_node_get_texture (node),
|
|
should_mipmap ? GSK_SCALING_FILTER_TRILINEAR : GSK_SCALING_FILTER_LINEAR);
|
|
*out_bounds = *clip_bounds;
|
|
return image;
|
|
}
|
|
|
|
if (should_mipmap)
|
|
return gsk_gpu_get_node_as_image_via_offscreen (frame, flags, ccs, clip_bounds, scale, node, out_bounds);
|
|
|
|
if (!gdk_color_state_equal (ccs, image_cs) ||
|
|
gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_STRAIGHT_ALPHA ||
|
|
((flags & GSK_GPU_AS_IMAGE_SAMPLED_OUT_OF_BOUNDS) &&
|
|
gdk_memory_format_alpha (gsk_gpu_image_get_format (image)) == GDK_MEMORY_ALPHA_OPAQUE))
|
|
{
|
|
image = gsk_gpu_copy_image (frame, ccs, image, image_cs, FALSE);
|
|
gsk_gpu_cache_cache_texture_image (gsk_gpu_device_get_cache (gsk_gpu_frame_get_device (frame)),
|
|
texture,
|
|
image,
|
|
ccs);
|
|
}
|
|
|
|
*out_bounds = node->bounds;
|
|
return image;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_texture_scale_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuImage *image;
|
|
GdkTexture *texture;
|
|
GdkColorState *image_cs;
|
|
GskScalingFilter scaling_filter;
|
|
gboolean need_mipmap, need_offscreen;
|
|
|
|
texture = gsk_texture_scale_node_get_texture (node);
|
|
scaling_filter = gsk_texture_scale_node_get_filter (node);
|
|
need_mipmap = scaling_filter == GSK_SCALING_FILTER_TRILINEAR;
|
|
image = gsk_gpu_lookup_texture (self->frame, self->ccs, texture, need_mipmap, &image_cs);
|
|
|
|
need_offscreen = image == NULL ||
|
|
self->modelview != NULL ||
|
|
!graphene_vec2_equal (&self->scale, graphene_vec2_one ());
|
|
|
|
if (need_offscreen)
|
|
{
|
|
GskGpuImage *offscreen;
|
|
graphene_rect_t clip_bounds;
|
|
|
|
gsk_gpu_node_processor_get_clip_bounds (self, &clip_bounds);
|
|
/* first round to pixel boundaries, so we make sure the full pixels are covered */
|
|
gsk_rect_snap_to_grid (&clip_bounds, &self->scale, &self->offset, &clip_bounds);
|
|
/* then expand by half a pixel so that pixels needed for eventual linear
|
|
* filtering are available */
|
|
graphene_rect_inset (&clip_bounds, -0.5, -0.5);
|
|
/* finally, round to full pixels */
|
|
gsk_rect_round_larger (&clip_bounds);
|
|
/* now intersect with actual node bounds */
|
|
if (!gsk_rect_intersection (&clip_bounds, &node->bounds, &clip_bounds))
|
|
{
|
|
g_object_unref (image);
|
|
return;
|
|
}
|
|
clip_bounds.size.width = ceilf (clip_bounds.size.width);
|
|
clip_bounds.size.height = ceilf (clip_bounds.size.height);
|
|
if (image == NULL)
|
|
{
|
|
offscreen = gsk_gpu_get_texture_tiles_as_image (self->frame,
|
|
self->ccs,
|
|
&clip_bounds,
|
|
graphene_vec2_one (),
|
|
&node->bounds,
|
|
texture,
|
|
scaling_filter);
|
|
}
|
|
else
|
|
{
|
|
offscreen = gsk_gpu_node_processor_create_offscreen (self->frame,
|
|
self->ccs,
|
|
graphene_vec2_one (),
|
|
&clip_bounds,
|
|
node);
|
|
g_object_unref (image);
|
|
}
|
|
gsk_gpu_texture_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
offscreen,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&clip_bounds
|
|
});
|
|
g_object_unref (offscreen);
|
|
return;
|
|
}
|
|
|
|
if ((gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_STRAIGHT_ALPHA) ||
|
|
(need_mipmap && !(gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_CAN_MIPMAP)) ||
|
|
!gdk_color_state_equal (image_cs, self->ccs))
|
|
{
|
|
image = gsk_gpu_copy_image (self->frame, self->ccs, image, image_cs, need_mipmap);
|
|
image_cs = self->ccs;
|
|
gsk_gpu_cache_cache_texture_image (gsk_gpu_device_get_cache (gsk_gpu_frame_get_device (self->frame)),
|
|
texture,
|
|
image,
|
|
image_cs);
|
|
}
|
|
|
|
if (need_mipmap && !(gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_MIPMAP))
|
|
gsk_gpu_mipmap_op (self->frame, image);
|
|
|
|
gsk_gpu_texture_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
gsk_gpu_sampler_for_scaling_filter (scaling_filter),
|
|
&node->bounds,
|
|
&node->bounds,
|
|
});
|
|
|
|
g_object_unref (image);
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_get_cairo_node_as_image (GskGpuFrame *frame,
|
|
GskGpuAsImageFlags flags,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
GskGpuImage *result;
|
|
|
|
if (!gdk_color_state_equal (ccs, GDK_COLOR_STATE_SRGB))
|
|
return gsk_gpu_get_node_as_image_via_offscreen (frame, flags, ccs, clip_bounds, scale, node, out_bounds);
|
|
|
|
result = gsk_gpu_upload_cairo_op (frame,
|
|
scale,
|
|
clip_bounds,
|
|
(GskGpuCairoFunc) gsk_render_node_draw_fallback,
|
|
gsk_render_node_ref (node),
|
|
(GDestroyNotify) gsk_render_node_unref);
|
|
|
|
g_object_ref (result);
|
|
|
|
*out_bounds = *clip_bounds;
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_inset_shadow_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
float spread, blur_radius;
|
|
const GdkColor *color;
|
|
|
|
color = gsk_inset_shadow_node_get_color2 (node);
|
|
spread = gsk_inset_shadow_node_get_spread (node);
|
|
blur_radius = gsk_inset_shadow_node_get_blur_radius (node);
|
|
|
|
if (blur_radius < 0.01)
|
|
{
|
|
GdkColor colors[4];
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
gdk_color_init_copy (&colors[i], color);
|
|
|
|
gsk_gpu_border_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
gsk_inset_shadow_node_get_outline (node),
|
|
gsk_inset_shadow_node_get_offset (node),
|
|
(float[4]) { spread, spread, spread, spread },
|
|
colors);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
gdk_color_finish (&colors[i]);
|
|
}
|
|
else
|
|
{
|
|
gsk_gpu_box_shadow_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
TRUE,
|
|
&node->bounds,
|
|
gsk_inset_shadow_node_get_outline (node),
|
|
gsk_inset_shadow_node_get_offset (node),
|
|
spread,
|
|
blur_radius,
|
|
color);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_outset_shadow_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
float spread, blur_radius;
|
|
const GdkColor *color;
|
|
const graphene_point_t *offset;
|
|
|
|
color = gsk_outset_shadow_node_get_color2 (node);
|
|
spread = gsk_outset_shadow_node_get_spread (node);
|
|
blur_radius = gsk_outset_shadow_node_get_blur_radius (node);
|
|
offset = gsk_outset_shadow_node_get_offset (node);
|
|
|
|
if (blur_radius < 0.01)
|
|
{
|
|
GskRoundedRect outline;
|
|
GdkColor colors[4];
|
|
|
|
gsk_rounded_rect_init_copy (&outline, gsk_outset_shadow_node_get_outline (node));
|
|
gsk_rounded_rect_shrink (&outline, -spread, -spread, -spread, -spread);
|
|
gsk_rect_init_offset (&outline.bounds, &outline.bounds, offset);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
gdk_color_init_copy (&colors[i], color);
|
|
|
|
gsk_gpu_border_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
&outline,
|
|
&GRAPHENE_POINT_INIT (- offset->x, - offset->y),
|
|
(float[4]) { spread, spread, spread, spread },
|
|
colors);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
gdk_color_finish (&colors[i]);
|
|
}
|
|
else
|
|
{
|
|
gsk_gpu_box_shadow_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
FALSE,
|
|
&node->bounds,
|
|
gsk_outset_shadow_node_get_outline (node),
|
|
offset,
|
|
spread,
|
|
blur_radius,
|
|
color);
|
|
}
|
|
}
|
|
|
|
typedef void (* GradientOpFunc) (GskGpuNodeProcessor *self,
|
|
GdkColorState *target,
|
|
GskRenderNode *node,
|
|
const GskColorStop2 *stops,
|
|
gsize n_stops);
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_gradient_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node,
|
|
GdkColorState *ics,
|
|
const GskColorStop2 *stops,
|
|
gsize n_stops,
|
|
GradientOpFunc func)
|
|
{
|
|
GskColorStop2 real_stops[7];
|
|
GskGpuNodeProcessor other;
|
|
graphene_rect_t bounds;
|
|
gsize i, j;
|
|
GskGpuImage *image;
|
|
GdkColorState *target;
|
|
|
|
if (GDK_IS_DEFAULT_COLOR_STATE (ics))
|
|
target = self->ccs;
|
|
else
|
|
target = ics;
|
|
|
|
if (n_stops < 8 && GDK_IS_DEFAULT_COLOR_STATE (ics))
|
|
{
|
|
func (self, target, node, stops, n_stops);
|
|
return;
|
|
}
|
|
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &bounds))
|
|
return;
|
|
gsk_rect_snap_to_grid (&bounds, &self->scale, &self->offset, &bounds);
|
|
|
|
image = gsk_gpu_node_processor_init_draw (&other,
|
|
self->frame,
|
|
self->ccs,
|
|
gdk_memory_depth_merge (gdk_color_state_get_depth (self->ccs),
|
|
gsk_render_node_get_preferred_depth (node)),
|
|
&self->scale,
|
|
&bounds);
|
|
|
|
other.blend = GSK_GPU_BLEND_ADD;
|
|
other.pending_globals |= GSK_GPU_GLOBAL_BLEND;
|
|
gsk_gpu_node_processor_sync_globals (&other, 0);
|
|
|
|
for (i = 0; i < n_stops; /* happens inside the loop */)
|
|
{
|
|
if (i == 0)
|
|
{
|
|
real_stops[0].offset = stops[i].offset;
|
|
gdk_color_init_copy (&real_stops[i].color, &stops[i].color);
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
real_stops[0].offset = stops[i-1].offset;
|
|
gdk_color_init_copy (&real_stops[0].color, &stops[i - 1].color);
|
|
real_stops[0].color.alpha *= 0;
|
|
}
|
|
for (j = 1; j < 6 && i < n_stops; j++)
|
|
{
|
|
real_stops[j].offset = stops[i].offset;
|
|
gdk_color_init_copy (&real_stops[j].color, &stops[i].color);
|
|
i++;
|
|
}
|
|
if (i == n_stops - 1)
|
|
{
|
|
g_assert (j == 6);
|
|
real_stops[j].offset = stops[i].offset;
|
|
gdk_color_init_copy (&real_stops[j].color, &stops[i].color);
|
|
j++;
|
|
i++;
|
|
}
|
|
else if (i < n_stops)
|
|
{
|
|
real_stops[j].offset = stops[i].offset;
|
|
gdk_color_init_copy (&real_stops[j].color, &stops[i].color);
|
|
real_stops[j].color.alpha *= 0;
|
|
j++;
|
|
}
|
|
|
|
func (&other, target, node, real_stops, j);
|
|
}
|
|
|
|
gsk_gpu_node_processor_finish_draw (&other, image);
|
|
|
|
if (GDK_IS_DEFAULT_COLOR_STATE (ics))
|
|
{
|
|
gsk_gpu_texture_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &bounds),
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&bounds
|
|
});
|
|
}
|
|
else
|
|
{
|
|
const GdkCicp *cicp = gdk_color_state_get_cicp (ics);
|
|
|
|
g_assert (cicp != NULL);
|
|
|
|
gsk_gpu_convert_from_cicp_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &bounds),
|
|
cicp,
|
|
gsk_gpu_color_states_create_cicp (self->ccs, TRUE, TRUE),
|
|
1,
|
|
TRUE,
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&bounds
|
|
});
|
|
}
|
|
|
|
g_object_unref (image);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_linear_gradient_op (GskGpuNodeProcessor *self,
|
|
GdkColorState *target,
|
|
GskRenderNode *node,
|
|
const GskColorStop2 *stops,
|
|
gsize n_stops)
|
|
{
|
|
gsk_gpu_linear_gradient_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
target,
|
|
self->opacity,
|
|
&self->offset,
|
|
gsk_linear_gradient_node_get_interpolation_color_state (node),
|
|
gsk_linear_gradient_node_get_hue_interpolation (node),
|
|
GSK_RENDER_NODE_TYPE (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE,
|
|
&node->bounds,
|
|
gsk_linear_gradient_node_get_start (node),
|
|
gsk_linear_gradient_node_get_end (node),
|
|
stops,
|
|
n_stops);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_linear_gradient_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
gsk_gpu_node_processor_add_gradient_node (self,
|
|
node,
|
|
gsk_linear_gradient_node_get_interpolation_color_state (node),
|
|
gsk_linear_gradient_node_get_color_stops2 (node),
|
|
gsk_linear_gradient_node_get_n_color_stops (node),
|
|
gsk_gpu_node_processor_linear_gradient_op);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_radial_gradient_op (GskGpuNodeProcessor *self,
|
|
GdkColorState *target,
|
|
GskRenderNode *node,
|
|
const GskColorStop2 *stops,
|
|
gsize n_stops)
|
|
{
|
|
gsk_gpu_radial_gradient_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
target,
|
|
self->opacity,
|
|
&self->offset,
|
|
gsk_radial_gradient_node_get_interpolation_color_state (node),
|
|
gsk_radial_gradient_node_get_hue_interpolation (node),
|
|
GSK_RENDER_NODE_TYPE (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE,
|
|
&node->bounds,
|
|
gsk_radial_gradient_node_get_center (node),
|
|
&GRAPHENE_POINT_INIT (
|
|
gsk_radial_gradient_node_get_hradius (node),
|
|
gsk_radial_gradient_node_get_vradius (node)
|
|
),
|
|
gsk_radial_gradient_node_get_start (node),
|
|
gsk_radial_gradient_node_get_end (node),
|
|
stops,
|
|
n_stops);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_radial_gradient_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
gsk_gpu_node_processor_add_gradient_node (self,
|
|
node,
|
|
gsk_radial_gradient_node_get_interpolation_color_state (node),
|
|
gsk_radial_gradient_node_get_color_stops2 (node),
|
|
gsk_radial_gradient_node_get_n_color_stops (node),
|
|
gsk_gpu_node_processor_radial_gradient_op);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_conic_gradient_op (GskGpuNodeProcessor *self,
|
|
GdkColorState *target,
|
|
GskRenderNode *node,
|
|
const GskColorStop2 *stops,
|
|
gsize n_stops)
|
|
{
|
|
gsk_gpu_conic_gradient_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
target,
|
|
self->opacity,
|
|
&self->offset,
|
|
gsk_conic_gradient_node_get_interpolation_color_state (node),
|
|
gsk_conic_gradient_node_get_hue_interpolation (node),
|
|
&node->bounds,
|
|
gsk_conic_gradient_node_get_center (node),
|
|
gsk_conic_gradient_node_get_angle (node),
|
|
stops,
|
|
n_stops);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_conic_gradient_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
gsk_gpu_node_processor_add_gradient_node (self,
|
|
node,
|
|
gsk_conic_gradient_node_get_interpolation_color_state (node),
|
|
gsk_conic_gradient_node_get_color_stops2 (node),
|
|
gsk_conic_gradient_node_get_n_color_stops (node),
|
|
gsk_gpu_node_processor_conic_gradient_op);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_blur_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNode *child;
|
|
GskGpuImage *image;
|
|
graphene_rect_t tex_rect, clip_rect;
|
|
float blur_radius, clip_radius;
|
|
|
|
child = gsk_blur_node_get_child (node);
|
|
blur_radius = gsk_blur_node_get_radius (node);
|
|
if (blur_radius <= 0.f)
|
|
{
|
|
gsk_gpu_node_processor_add_node (self, child);
|
|
return;
|
|
}
|
|
|
|
clip_radius = gsk_cairo_blur_compute_pixels (blur_radius / 2.0);
|
|
gsk_gpu_node_processor_get_clip_bounds (self, &clip_rect);
|
|
graphene_rect_inset (&clip_rect, -clip_radius, -clip_radius);
|
|
image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
GSK_GPU_AS_IMAGE_SAMPLED_OUT_OF_BOUNDS,
|
|
&clip_rect,
|
|
child,
|
|
&tex_rect);
|
|
if (image == NULL)
|
|
return;
|
|
|
|
gsk_gpu_node_processor_blur_op (self,
|
|
&node->bounds,
|
|
graphene_point_zero (),
|
|
blur_radius,
|
|
NULL,
|
|
image,
|
|
gdk_memory_format_get_depth (gsk_gpu_image_get_format (image),
|
|
gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_SRGB),
|
|
&tex_rect);
|
|
|
|
g_object_unref (image);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_shadow_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuImage *image;
|
|
graphene_rect_t clip_bounds, tex_rect;
|
|
GskRenderNode *child;
|
|
gsize i, n_shadows;
|
|
|
|
n_shadows = gsk_shadow_node_get_n_shadows (node);
|
|
child = gsk_shadow_node_get_child (node);
|
|
/* enlarge clip for shadow offsets */
|
|
gsk_gpu_node_processor_get_clip_bounds (self, &clip_bounds);
|
|
clip_bounds = GRAPHENE_RECT_INIT (clip_bounds.origin.x - node->bounds.size.width + child->bounds.size.width - node->bounds.origin.x + child->bounds.origin.x,
|
|
clip_bounds.origin.y - node->bounds.size.height + child->bounds.size.height - node->bounds.origin.y + child->bounds.origin.y,
|
|
clip_bounds.size.width + node->bounds.size.width - child->bounds.size.width,
|
|
clip_bounds.size.height + node->bounds.size.height - child->bounds.size.height);
|
|
|
|
image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
GSK_GPU_AS_IMAGE_SAMPLED_OUT_OF_BOUNDS,
|
|
&clip_bounds,
|
|
child,
|
|
&tex_rect);
|
|
if (image == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < n_shadows; i++)
|
|
{
|
|
const GskShadow2 *shadow = gsk_shadow_node_get_shadow2 (node, i);
|
|
|
|
if (shadow->radius == 0)
|
|
{
|
|
graphene_point_t shadow_offset = GRAPHENE_POINT_INIT (self->offset.x + shadow->offset.x,
|
|
self->offset.y + shadow->offset.y);
|
|
|
|
gsk_gpu_colorize_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &shadow_offset, &child->bounds),
|
|
self->ccs,
|
|
1,
|
|
&shadow_offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_TRANSPARENT,
|
|
&child->bounds,
|
|
&tex_rect,
|
|
},
|
|
&shadow->color);
|
|
}
|
|
else
|
|
{
|
|
graphene_rect_t bounds;
|
|
float clip_radius = gsk_cairo_blur_compute_pixels (0.5 * shadow->radius);
|
|
graphene_rect_inset_r (&child->bounds, - clip_radius, - clip_radius, &bounds);
|
|
gsk_gpu_node_processor_blur_op (self,
|
|
&bounds,
|
|
&shadow->offset,
|
|
shadow->radius,
|
|
&shadow->color,
|
|
image,
|
|
gdk_memory_format_get_depth (gsk_gpu_image_get_format (image),
|
|
gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_SRGB),
|
|
&tex_rect);
|
|
}
|
|
}
|
|
|
|
gsk_gpu_texture_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &child->bounds),
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&child->bounds,
|
|
&tex_rect,
|
|
});
|
|
|
|
g_object_unref (image);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_gl_shader_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
gsk_gpu_color_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
&node->bounds,
|
|
&GDK_COLOR_SRGB (1, 105/255., 180/255., 1));
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_blend_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNode *bottom_child, *top_child;
|
|
graphene_rect_t bottom_rect, top_rect;
|
|
GskGpuImage *bottom_image, *top_image;
|
|
|
|
bottom_child = gsk_blend_node_get_bottom_child (node);
|
|
top_child = gsk_blend_node_get_top_child (node);
|
|
|
|
bottom_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
NULL,
|
|
bottom_child,
|
|
&bottom_rect);
|
|
top_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
NULL,
|
|
top_child,
|
|
&top_rect);
|
|
|
|
if (bottom_image == NULL)
|
|
{
|
|
if (top_image == NULL)
|
|
return;
|
|
|
|
bottom_image = g_object_ref (top_image);
|
|
bottom_rect = *graphene_rect_zero ();
|
|
}
|
|
else if (top_image == NULL)
|
|
{
|
|
top_image = g_object_ref (bottom_image);
|
|
top_rect = *graphene_rect_zero ();
|
|
}
|
|
|
|
gsk_gpu_blend_mode_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
&node->bounds,
|
|
&self->offset,
|
|
self->opacity,
|
|
gsk_blend_node_get_blend_mode (node),
|
|
&(GskGpuShaderImage) {
|
|
bottom_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&bottom_rect
|
|
},
|
|
&(GskGpuShaderImage) {
|
|
top_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&top_rect
|
|
});
|
|
|
|
g_object_unref (top_image);
|
|
g_object_unref (bottom_image);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_cross_fade_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNode *start_child, *end_child;
|
|
graphene_rect_t start_rect, end_rect;
|
|
GskGpuImage *start_image, *end_image;
|
|
float progress, old_opacity;
|
|
|
|
start_child = gsk_cross_fade_node_get_start_child (node);
|
|
end_child = gsk_cross_fade_node_get_end_child (node);
|
|
progress = gsk_cross_fade_node_get_progress (node);
|
|
|
|
if (progress <= 0.0)
|
|
{
|
|
gsk_gpu_node_processor_add_node (self, start_child);
|
|
return;
|
|
}
|
|
if (progress >= 1.0)
|
|
{
|
|
gsk_gpu_node_processor_add_node (self, end_child);
|
|
return;
|
|
}
|
|
|
|
start_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
NULL,
|
|
start_child,
|
|
&start_rect);
|
|
end_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
NULL,
|
|
end_child,
|
|
&end_rect);
|
|
|
|
if (start_image == NULL)
|
|
{
|
|
if (end_image == NULL)
|
|
return;
|
|
|
|
old_opacity = self->opacity;
|
|
self->opacity *= progress;
|
|
gsk_gpu_node_processor_image_op (self,
|
|
end_image,
|
|
self->ccs,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&end_child->bounds,
|
|
&end_rect);
|
|
g_object_unref (end_image);
|
|
self->opacity = old_opacity;
|
|
return;
|
|
}
|
|
else if (end_image == NULL)
|
|
{
|
|
old_opacity = self->opacity;
|
|
self->opacity *= (1 - progress);
|
|
gsk_gpu_node_processor_image_op (self,
|
|
start_image,
|
|
self->ccs,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&start_child->bounds,
|
|
&start_rect);
|
|
g_object_unref (start_image);
|
|
self->opacity = old_opacity;
|
|
return;
|
|
}
|
|
|
|
gsk_gpu_cross_fade_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
&node->bounds,
|
|
&self->offset,
|
|
self->opacity,
|
|
progress,
|
|
&(GskGpuShaderImage) {
|
|
start_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&start_rect
|
|
},
|
|
&(GskGpuShaderImage) {
|
|
end_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&end_rect
|
|
});
|
|
|
|
g_object_unref (end_image);
|
|
g_object_unref (start_image);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_mask_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNode *source_child, *mask_child;
|
|
GskGpuImage *mask_image;
|
|
graphene_rect_t bounds, mask_rect;
|
|
GskMaskMode mask_mode;
|
|
|
|
source_child = gsk_mask_node_get_source (node);
|
|
mask_child = gsk_mask_node_get_mask (node);
|
|
mask_mode = gsk_mask_node_get_mask_mode (node);
|
|
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &bounds))
|
|
return;
|
|
|
|
mask_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
&bounds,
|
|
mask_child,
|
|
&mask_rect);
|
|
if (mask_image == NULL)
|
|
{
|
|
if (mask_mode == GSK_MASK_MODE_INVERTED_ALPHA)
|
|
gsk_gpu_node_processor_add_node (self, source_child);
|
|
return;
|
|
}
|
|
|
|
if (gsk_render_node_get_node_type (source_child) == GSK_COLOR_NODE &&
|
|
mask_mode == GSK_MASK_MODE_ALPHA)
|
|
{
|
|
gsk_gpu_colorize_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
self->opacity,
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
mask_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&mask_rect,
|
|
},
|
|
gsk_color_node_get_color2 (source_child));
|
|
}
|
|
else
|
|
{
|
|
GskGpuImage *source_image;
|
|
graphene_rect_t source_rect;
|
|
|
|
source_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
&bounds,
|
|
source_child,
|
|
&source_rect);
|
|
if (source_image == NULL)
|
|
{
|
|
g_object_unref (mask_image);
|
|
return;
|
|
}
|
|
|
|
gsk_gpu_mask_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
&node->bounds,
|
|
&self->offset,
|
|
self->opacity,
|
|
mask_mode,
|
|
&(GskGpuShaderImage) {
|
|
source_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&source_rect,
|
|
},
|
|
&(GskGpuShaderImage) {
|
|
mask_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&mask_rect,
|
|
});
|
|
|
|
g_object_unref (source_image);
|
|
}
|
|
|
|
g_object_unref (mask_image);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_glyph_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuCache *cache;
|
|
const PangoGlyphInfo *glyphs;
|
|
PangoFont *font;
|
|
graphene_point_t offset;
|
|
guint i, num_glyphs;
|
|
float scale;
|
|
float align_scale_x, align_scale_y;
|
|
float inv_align_scale_x, inv_align_scale_y;
|
|
unsigned int flags_mask;
|
|
const float inv_pango_scale = 1.f / PANGO_SCALE;
|
|
cairo_hint_style_t hint_style;
|
|
const GdkColor *color;
|
|
GdkColorState *alt;
|
|
GskGpuColorStates color_states;
|
|
GdkColor color2;
|
|
GskGpuShaderClip node_clip;
|
|
|
|
if (self->opacity < 1.0 &&
|
|
gsk_text_node_has_color_glyphs (node))
|
|
{
|
|
gsk_gpu_node_processor_add_without_opacity (self, node);
|
|
return;
|
|
}
|
|
|
|
cache = gsk_gpu_device_get_cache (gsk_gpu_frame_get_device (self->frame));
|
|
|
|
num_glyphs = gsk_text_node_get_num_glyphs (node);
|
|
glyphs = gsk_text_node_get_glyphs (node, NULL);
|
|
font = gsk_text_node_get_font (node);
|
|
offset = *gsk_text_node_get_offset (node);
|
|
hint_style = gsk_text_node_get_font_hint_style (node);
|
|
color = gsk_text_node_get_color2 (node);
|
|
|
|
alt = gsk_gpu_color_states_find (self->ccs, color);
|
|
color_states = gsk_gpu_color_states_create (self->ccs, TRUE, alt, FALSE);
|
|
gdk_color_convert (&color2, alt, color);
|
|
|
|
node_clip = gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
|
|
offset.x += self->offset.x;
|
|
offset.y += self->offset.y;
|
|
|
|
scale = MAX (graphene_vec2_get_x (&self->scale), graphene_vec2_get_y (&self->scale));
|
|
|
|
if (hint_style != CAIRO_HINT_STYLE_NONE)
|
|
{
|
|
align_scale_x = scale * 4;
|
|
align_scale_y = scale;
|
|
flags_mask = 3;
|
|
}
|
|
else
|
|
{
|
|
align_scale_x = align_scale_y = scale * 4;
|
|
flags_mask = 15;
|
|
}
|
|
|
|
inv_align_scale_x = 1 / align_scale_x;
|
|
inv_align_scale_y = 1 / align_scale_y;
|
|
|
|
for (i = 0; i < num_glyphs; i++)
|
|
{
|
|
GskGpuImage *image;
|
|
graphene_rect_t glyph_bounds, glyph_tex_rect;
|
|
graphene_point_t glyph_offset, glyph_origin;
|
|
GskGpuGlyphLookupFlags flags;
|
|
GskGpuShaderClip glyph_clip;
|
|
|
|
glyph_origin = GRAPHENE_POINT_INIT (offset.x + glyphs[i].geometry.x_offset * inv_pango_scale,
|
|
offset.y + glyphs[i].geometry.y_offset * inv_pango_scale);
|
|
|
|
glyph_origin.x = floorf (glyph_origin.x * align_scale_x + 0.5f);
|
|
glyph_origin.y = floorf (glyph_origin.y * align_scale_y + 0.5f);
|
|
flags = (((int) glyph_origin.x & 3) | (((int) glyph_origin.y & 3) << 2)) & flags_mask;
|
|
glyph_origin.x *= inv_align_scale_x;
|
|
glyph_origin.y *= inv_align_scale_y;
|
|
|
|
image = gsk_gpu_cache_lookup_glyph_image (cache,
|
|
self->frame,
|
|
font,
|
|
glyphs[i].glyph,
|
|
flags,
|
|
scale,
|
|
&glyph_bounds,
|
|
&glyph_offset);
|
|
|
|
glyph_tex_rect = GRAPHENE_RECT_INIT (-glyph_bounds.origin.x / scale,
|
|
-glyph_bounds.origin.y / scale,
|
|
gsk_gpu_image_get_width (image) / scale,
|
|
gsk_gpu_image_get_height (image) / scale);
|
|
glyph_bounds = GRAPHENE_RECT_INIT (0,
|
|
0,
|
|
glyph_bounds.size.width / scale,
|
|
glyph_bounds.size.height / scale);
|
|
glyph_origin = GRAPHENE_POINT_INIT (glyph_origin.x - glyph_offset.x / scale,
|
|
glyph_origin.y - glyph_offset.y / scale);
|
|
|
|
if (node_clip == GSK_GPU_SHADER_CLIP_NONE)
|
|
glyph_clip = GSK_GPU_SHADER_CLIP_NONE;
|
|
else
|
|
glyph_clip = gsk_gpu_clip_get_shader_clip (&self->clip, &glyph_origin, &glyph_bounds);
|
|
|
|
if (glyphs[i].attr.is_color)
|
|
gsk_gpu_texture_op (self->frame,
|
|
glyph_clip,
|
|
&glyph_origin,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&glyph_bounds,
|
|
&glyph_tex_rect
|
|
});
|
|
else
|
|
gsk_gpu_colorize_op2 (self->frame,
|
|
glyph_clip,
|
|
color_states,
|
|
self->opacity,
|
|
&glyph_origin,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&glyph_bounds,
|
|
&glyph_tex_rect
|
|
},
|
|
&color2);
|
|
|
|
offset.x += glyphs[i].geometry.width * inv_pango_scale;
|
|
}
|
|
|
|
gdk_color_finish (&color2);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_color_matrix_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskGpuImage *image;
|
|
GskRenderNode *child;
|
|
graphene_matrix_t opacity_matrix;
|
|
const graphene_matrix_t *color_matrix;
|
|
graphene_rect_t tex_rect;
|
|
|
|
child = gsk_color_matrix_node_get_child (node);
|
|
|
|
color_matrix = gsk_color_matrix_node_get_color_matrix (node);
|
|
if (self->opacity < 1.0f)
|
|
{
|
|
graphene_matrix_init_from_float (&opacity_matrix,
|
|
(float[16]) {
|
|
1.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, 1.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f, self->opacity
|
|
});
|
|
graphene_matrix_multiply (&opacity_matrix, color_matrix, &opacity_matrix);
|
|
color_matrix = &opacity_matrix;
|
|
}
|
|
|
|
image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
NULL,
|
|
child,
|
|
&tex_rect);
|
|
if (image == NULL)
|
|
return;
|
|
|
|
gsk_gpu_color_matrix_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
gsk_gpu_node_processor_color_states_explicit (self, self->ccs, FALSE),
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&node->bounds,
|
|
&tex_rect,
|
|
},
|
|
color_matrix,
|
|
gsk_color_matrix_node_get_color_offset (node));
|
|
|
|
g_object_unref (image);
|
|
}
|
|
|
|
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;
|
|
|
|
gsk_rect_init_offset (&offset_rect,
|
|
rect,
|
|
&GRAPHENE_POINT_INIT (- x * child_bounds->size.width,
|
|
- y * child_bounds->size.height));
|
|
if (!gsk_rect_intersection (&offset_rect, child_bounds, &clipped_child_bounds))
|
|
{
|
|
/* rounding error hits again */
|
|
return;
|
|
}
|
|
|
|
GSK_DEBUG (FALLBACK, "Offscreening node '%s' for tiling", g_type_name_from_instance ((GTypeInstance *) child));
|
|
image = gsk_gpu_node_processor_create_offscreen (self->frame,
|
|
self->ccs,
|
|
&self->scale,
|
|
&clipped_child_bounds,
|
|
child);
|
|
|
|
g_return_if_fail (image);
|
|
|
|
gsk_gpu_texture_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, rect),
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_REPEAT,
|
|
rect,
|
|
&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;
|
|
gboolean avoid_offscreen;
|
|
|
|
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;
|
|
avoid_offscreen = !gsk_gpu_frame_should_optimize (self->frame, GSK_GPU_OPTIMIZE_REPEAT);
|
|
|
|
/* 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 (!avoid_offscreen &&
|
|
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 (!avoid_offscreen &&
|
|
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;
|
|
}
|
|
}
|
|
|
|
typedef struct _FillData FillData;
|
|
struct _FillData
|
|
{
|
|
GskPath *path;
|
|
GdkColor color;
|
|
GskFillRule fill_rule;
|
|
};
|
|
|
|
static void
|
|
gsk_fill_data_free (gpointer data)
|
|
{
|
|
FillData *fill = data;
|
|
|
|
gdk_color_finish (&fill->color);
|
|
gsk_path_unref (fill->path);
|
|
g_free (fill);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_fill_path (gpointer data,
|
|
cairo_t *cr)
|
|
{
|
|
FillData *fill = data;
|
|
|
|
switch (fill->fill_rule)
|
|
{
|
|
case GSK_FILL_RULE_WINDING:
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
|
|
break;
|
|
case GSK_FILL_RULE_EVEN_ODD:
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
gsk_path_to_cairo (fill->path, cr);
|
|
gdk_cairo_set_source_color (cr, GDK_COLOR_STATE_SRGB, &fill->color);
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_fill_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
graphene_rect_t clip_bounds, source_rect;
|
|
GskGpuImage *mask_image, *source_image;
|
|
GskRenderNode *child;
|
|
GdkColor color;
|
|
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip_bounds))
|
|
return;
|
|
gsk_rect_snap_to_grid (&clip_bounds, &self->scale, &self->offset, &clip_bounds);
|
|
|
|
child = gsk_fill_node_get_child (node);
|
|
|
|
if (GSK_RENDER_NODE_TYPE (child) == GSK_COLOR_NODE)
|
|
gdk_color_init_copy (&color, gsk_color_node_get_color2 (child));
|
|
else
|
|
gdk_color_init (&color, GDK_COLOR_STATE_SRGB, (float[]) { 1, 1, 1, 1 });
|
|
|
|
mask_image = gsk_gpu_upload_cairo_op (self->frame,
|
|
&self->scale,
|
|
&clip_bounds,
|
|
gsk_gpu_node_processor_fill_path,
|
|
g_memdup2 (&(FillData) {
|
|
.path = gsk_path_ref (gsk_fill_node_get_path (node)),
|
|
.color = color,
|
|
.fill_rule = gsk_fill_node_get_fill_rule (node)
|
|
}, sizeof (FillData)),
|
|
(GDestroyNotify) gsk_fill_data_free);
|
|
g_return_if_fail (mask_image != NULL);
|
|
if (GSK_RENDER_NODE_TYPE (child) == GSK_COLOR_NODE)
|
|
{
|
|
gsk_gpu_node_processor_image_op (self,
|
|
mask_image,
|
|
GDK_COLOR_STATE_SRGB,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&clip_bounds,
|
|
&clip_bounds);
|
|
return;
|
|
}
|
|
|
|
source_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
&clip_bounds,
|
|
child,
|
|
&source_rect);
|
|
if (source_image == NULL)
|
|
return;
|
|
|
|
gsk_gpu_mask_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &clip_bounds),
|
|
&clip_bounds,
|
|
&self->offset,
|
|
self->opacity,
|
|
GSK_MASK_MODE_ALPHA,
|
|
&(GskGpuShaderImage) {
|
|
source_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&source_rect,
|
|
},
|
|
&(GskGpuShaderImage) {
|
|
mask_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&clip_bounds,
|
|
});
|
|
|
|
g_object_unref (source_image);
|
|
}
|
|
|
|
typedef struct _StrokeData StrokeData;
|
|
struct _StrokeData
|
|
{
|
|
GskPath *path;
|
|
GdkColor color;
|
|
GskStroke stroke;
|
|
};
|
|
|
|
static void
|
|
gsk_stroke_data_free (gpointer data)
|
|
{
|
|
StrokeData *stroke = data;
|
|
|
|
gdk_color_finish (&stroke->color);
|
|
gsk_path_unref (stroke->path);
|
|
gsk_stroke_clear (&stroke->stroke);
|
|
g_free (stroke);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_stroke_path (gpointer data,
|
|
cairo_t *cr)
|
|
{
|
|
StrokeData *stroke = data;
|
|
|
|
gsk_stroke_to_cairo (&stroke->stroke, cr);
|
|
gsk_path_to_cairo (stroke->path, cr);
|
|
gdk_cairo_set_source_color (cr, GDK_COLOR_STATE_SRGB, &stroke->color);
|
|
cairo_stroke (cr);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_stroke_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
graphene_rect_t clip_bounds, source_rect;
|
|
GskGpuImage *mask_image, *source_image;
|
|
GskRenderNode *child;
|
|
GdkColor color;
|
|
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip_bounds))
|
|
return;
|
|
gsk_rect_snap_to_grid (&clip_bounds, &self->scale, &self->offset, &clip_bounds);
|
|
|
|
child = gsk_stroke_node_get_child (node);
|
|
|
|
if (GSK_RENDER_NODE_TYPE (child) == GSK_COLOR_NODE)
|
|
gdk_color_init_copy (&color, gsk_color_node_get_color2 (child));
|
|
else
|
|
gdk_color_init (&color, GDK_COLOR_STATE_SRGB, (float[]) { 1, 1, 1, 1 });
|
|
|
|
mask_image = gsk_gpu_upload_cairo_op (self->frame,
|
|
&self->scale,
|
|
&clip_bounds,
|
|
gsk_gpu_node_processor_stroke_path,
|
|
g_memdup2 (&(StrokeData) {
|
|
.path = gsk_path_ref (gsk_stroke_node_get_path (node)),
|
|
.color = color,
|
|
.stroke = GSK_STROKE_INIT_COPY (gsk_stroke_node_get_stroke (node))
|
|
}, sizeof (StrokeData)),
|
|
(GDestroyNotify) gsk_stroke_data_free);
|
|
g_return_if_fail (mask_image != NULL);
|
|
if (GSK_RENDER_NODE_TYPE (child) == GSK_COLOR_NODE)
|
|
{
|
|
gsk_gpu_node_processor_image_op (self,
|
|
mask_image,
|
|
GDK_COLOR_STATE_SRGB,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&clip_bounds,
|
|
&clip_bounds);
|
|
return;
|
|
}
|
|
|
|
source_image = gsk_gpu_node_processor_get_node_as_image (self,
|
|
0,
|
|
&clip_bounds,
|
|
child,
|
|
&source_rect);
|
|
if (source_image == NULL)
|
|
return;
|
|
|
|
gsk_gpu_mask_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &clip_bounds),
|
|
&clip_bounds,
|
|
&self->offset,
|
|
self->opacity,
|
|
GSK_MASK_MODE_ALPHA,
|
|
&(GskGpuShaderImage) {
|
|
source_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&source_rect,
|
|
},
|
|
&(GskGpuShaderImage) {
|
|
mask_image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
NULL,
|
|
&clip_bounds,
|
|
});
|
|
|
|
g_object_unref (source_image);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_subsurface_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GdkSubsurface *subsurface;
|
|
|
|
subsurface = gsk_subsurface_node_get_subsurface (node);
|
|
if (subsurface == NULL ||
|
|
gdk_subsurface_get_texture (subsurface) == NULL ||
|
|
gdk_subsurface_get_parent (subsurface) != gdk_draw_context_get_surface (gsk_gpu_frame_get_context (self->frame)))
|
|
{
|
|
gsk_gpu_node_processor_add_node (self, gsk_subsurface_node_get_child (node));
|
|
return;
|
|
}
|
|
|
|
if (!gdk_subsurface_is_above_parent (subsurface))
|
|
{
|
|
cairo_rectangle_int_t int_clipped;
|
|
graphene_rect_t rect, clipped;
|
|
|
|
gsk_rect_init_offset (&rect, &node->bounds, &self->offset);
|
|
gsk_rect_intersection (&self->clip.rect.bounds, &rect, &clipped);
|
|
|
|
if (gsk_gpu_frame_should_optimize (self->frame, GSK_GPU_OPTIMIZE_CLEAR) &&
|
|
node->bounds.size.width * node->bounds.size.height > 100 * 100 && /* not worth the effort for small images */
|
|
(self->clip.type != GSK_GPU_CLIP_ROUNDED ||
|
|
gsk_gpu_clip_contains_rect (&self->clip, &GRAPHENE_POINT_INIT(0,0), &clipped)) &&
|
|
gsk_gpu_node_processor_rect_is_integer (self, &clipped, &int_clipped))
|
|
{
|
|
if (gdk_rectangle_intersect (&int_clipped, &self->scissor, &int_clipped))
|
|
{
|
|
float color[4] = { 0, 0, 0, 0 };
|
|
gsk_gpu_clear_op (self->frame, &int_clipped, color);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->blend = GSK_GPU_BLEND_CLEAR;
|
|
self->pending_globals |= GSK_GPU_GLOBAL_BLEND;
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
GdkColor white;
|
|
|
|
gdk_color_init (&white, self->ccs, ((float[]) { 1, 1, 1, 1 }));
|
|
gsk_gpu_color_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds),
|
|
self->ccs,
|
|
1,
|
|
&self->offset,
|
|
&node->bounds,
|
|
&white);
|
|
gdk_color_finish (&white);
|
|
|
|
self->blend = GSK_GPU_BLEND_OVER;
|
|
self->pending_globals |= GSK_GPU_GLOBAL_BLEND;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_subsurface_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node)
|
|
{
|
|
GdkSubsurface *subsurface;
|
|
|
|
subsurface = gsk_subsurface_node_get_subsurface (node);
|
|
if (subsurface == NULL ||
|
|
gdk_subsurface_get_texture (subsurface) == NULL ||
|
|
gdk_subsurface_get_parent (subsurface) != gdk_draw_context_get_surface (gsk_gpu_frame_get_context (self->frame)))
|
|
{
|
|
return gsk_gpu_node_processor_add_first_node (self,
|
|
info,
|
|
gsk_subsurface_node_get_child (node));
|
|
}
|
|
|
|
if (gdk_subsurface_is_above_parent (subsurface))
|
|
return FALSE;
|
|
|
|
if (!gsk_gpu_node_processor_clip_first_node (self, info, &node->bounds))
|
|
return FALSE;
|
|
|
|
gsk_gpu_first_node_begin_rendering (self, info, GSK_VEC4_TRANSPARENT);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_get_subsurface_node_as_image (GskGpuFrame *frame,
|
|
GskGpuAsImageFlags flags,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
#ifndef G_DISABLE_ASSERT
|
|
GdkSubsurface *subsurface;
|
|
|
|
subsurface = gsk_subsurface_node_get_subsurface (node);
|
|
g_assert (subsurface == NULL ||
|
|
gdk_subsurface_get_texture (subsurface) == NULL ||
|
|
gdk_subsurface_get_parent (subsurface) != gdk_draw_context_get_surface (gsk_gpu_frame_get_context (frame)));
|
|
#endif
|
|
|
|
return gsk_gpu_get_node_as_image (frame,
|
|
flags,
|
|
ccs,
|
|
clip_bounds,
|
|
scale,
|
|
gsk_subsurface_node_get_child (node),
|
|
out_bounds);
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_container_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNode **children;
|
|
guint n_children;
|
|
|
|
if (self->opacity < 1.0 && !gsk_container_node_is_disjoint (node))
|
|
{
|
|
gsk_gpu_node_processor_add_without_opacity (self, node);
|
|
return;
|
|
}
|
|
|
|
children = gsk_container_node_get_children (node, &n_children);
|
|
for (guint i = 0; i < n_children; i++)
|
|
gsk_gpu_node_processor_add_node (self, children[i]);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_container_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNode **children;
|
|
graphene_rect_t opaque;
|
|
int i;
|
|
guint n;
|
|
|
|
children = gsk_container_node_get_children (node, &n);
|
|
if (n == 0)
|
|
return FALSE;
|
|
|
|
if (!gsk_render_node_get_opaque_rect (node, &opaque) ||
|
|
!gsk_gpu_node_processor_clip_first_node (self, info, &opaque))
|
|
return FALSE;
|
|
|
|
for (i = n; i-->0; )
|
|
{
|
|
if (gsk_gpu_node_processor_add_first_node (self, info, children[i]))
|
|
break;
|
|
}
|
|
|
|
if (i < 0)
|
|
gsk_gpu_first_node_begin_rendering (self, info, NULL);
|
|
|
|
for (i++; i < n; i++)
|
|
gsk_gpu_node_processor_add_node (self, children[i]);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_debug_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
gsk_gpu_node_processor_add_node (self, gsk_debug_node_get_child (node));
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_debug_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node)
|
|
{
|
|
return gsk_gpu_node_processor_add_first_node (self,
|
|
info,
|
|
gsk_debug_node_get_child (node));
|
|
}
|
|
|
|
static GskGpuImage *
|
|
gsk_gpu_get_debug_node_as_image (GskGpuFrame *frame,
|
|
GskGpuAsImageFlags flags,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
return gsk_gpu_get_node_as_image (frame,
|
|
flags,
|
|
ccs,
|
|
clip_bounds,
|
|
scale,
|
|
gsk_debug_node_get_child (node),
|
|
out_bounds);
|
|
}
|
|
|
|
typedef enum {
|
|
GSK_GPU_HANDLE_OPACITY = (1 << 0)
|
|
} GskGpuNodeFeatures;
|
|
|
|
static const struct
|
|
{
|
|
GskGpuGlobals ignored_globals;
|
|
GskGpuNodeFeatures features;
|
|
void (* process_node) (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node);
|
|
gboolean (* process_first_node) (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node);
|
|
GskGpuImage * (* get_node_as_image) (GskGpuFrame *self,
|
|
GskGpuAsImageFlags flags,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds);
|
|
} nodes_vtable[] = {
|
|
[GSK_NOT_A_RENDER_NODE] = {
|
|
0,
|
|
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,
|
|
gsk_gpu_node_processor_add_cairo_node,
|
|
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] = {
|
|
0,
|
|
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] = {
|
|
0,
|
|
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] = {
|
|
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_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] = {
|
|
0,
|
|
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,
|
|
gsk_gpu_node_processor_add_first_debug_node,
|
|
gsk_gpu_get_debug_node_as_image,
|
|
},
|
|
[GSK_GL_SHADER_NODE] = {
|
|
0,
|
|
GSK_GPU_HANDLE_OPACITY,
|
|
gsk_gpu_node_processor_add_gl_shader_node,
|
|
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,
|
|
gsk_gpu_node_processor_add_first_subsurface_node,
|
|
gsk_gpu_get_subsurface_node_as_image,
|
|
},
|
|
};
|
|
|
|
static void
|
|
gsk_gpu_node_processor_add_node (GskGpuNodeProcessor *self,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNodeType node_type;
|
|
|
|
/* 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)
|
|
return;
|
|
|
|
if (!gsk_gpu_clip_may_intersect_rect (&self->clip, &self->offset, &node->bounds))
|
|
return;
|
|
|
|
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;
|
|
}
|
|
|
|
if (self->opacity < 1.0 && (nodes_vtable[node_type].features & GSK_GPU_HANDLE_OPACITY) == 0)
|
|
{
|
|
gsk_gpu_node_processor_add_without_opacity (self, node);
|
|
return;
|
|
}
|
|
|
|
gsk_gpu_node_processor_sync_globals (self, nodes_vtable[node_type].ignored_globals);
|
|
g_assert ((self->pending_globals & ~nodes_vtable[node_type].ignored_globals) == 0);
|
|
|
|
if (nodes_vtable[node_type].process_node)
|
|
{
|
|
nodes_vtable[node_type].process_node (self, node);
|
|
}
|
|
else
|
|
{
|
|
g_warning_once ("Unimplemented node '%s'",
|
|
g_type_name_from_instance ((GTypeInstance *) node));
|
|
/* Maybe it's implemented in the Cairo renderer? */
|
|
gsk_gpu_node_processor_add_cairo_node (self, node);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gpu_node_processor_add_first_node (GskGpuNodeProcessor *self,
|
|
GskGpuFirstNodeInfo *info,
|
|
GskRenderNode *node)
|
|
{
|
|
GskRenderNodeType node_type;
|
|
graphene_rect_t opaque;
|
|
|
|
/* 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 FALSE;
|
|
|
|
if (!gsk_gpu_clip_may_intersect_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, info, node);
|
|
|
|
/* fallback starts here */
|
|
if (!gsk_gpu_node_processor_clip_first_node (self, info, &opaque))
|
|
return FALSE;
|
|
|
|
gsk_gpu_first_node_begin_rendering (self, info, NULL);
|
|
gsk_gpu_node_processor_add_node (self, node);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* gsk_gpu_get_node_as_image:
|
|
* @frame: frame to render in
|
|
* @flags: flags for the image
|
|
* @ccs: the color state to composite the image in
|
|
* @clip_bounds: region of node that must be included in image
|
|
* @scale: scale factor to use for the image
|
|
* @node: the node to render
|
|
* @out_bounds: the actual bounds of the result
|
|
*
|
|
* Get the part of the node indicated by the clip bounds as an image.
|
|
*
|
|
* The resulting image will be in the given colorstate and premultiplied.
|
|
*
|
|
* It is perfectly valid for this function to return an image covering
|
|
* a larger or smaller rectangle than the given clip bounds.
|
|
* It can be smaller if the node is actually smaller than the clip
|
|
* bounds and it's not necessary to create such a large offscreen, and
|
|
* it can be larger if only part of a node is drawn but a cached image
|
|
* for the full node (usually a texture node) already exists.
|
|
*
|
|
* The rectangle that is actually covered by the image is returned in
|
|
* out_bounds.
|
|
*
|
|
* Returns: the image or %NULL if there was nothing to render
|
|
**/
|
|
static GskGpuImage *
|
|
gsk_gpu_get_node_as_image (GskGpuFrame *frame,
|
|
GskGpuAsImageFlags flags,
|
|
GdkColorState *ccs,
|
|
const graphene_rect_t *clip_bounds,
|
|
const graphene_vec2_t *scale,
|
|
GskRenderNode *node,
|
|
graphene_rect_t *out_bounds)
|
|
{
|
|
GskRenderNodeType node_type;
|
|
|
|
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 NULL;
|
|
}
|
|
|
|
if (gsk_gpu_frame_should_optimize (frame, GSK_GPU_OPTIMIZE_TO_IMAGE) &&
|
|
nodes_vtable[node_type].get_node_as_image)
|
|
{
|
|
return nodes_vtable[node_type].get_node_as_image (frame, flags, ccs, clip_bounds, scale, node, out_bounds);
|
|
}
|
|
else
|
|
{
|
|
GSK_DEBUG (FALLBACK, "Unsupported node '%s'",
|
|
g_type_name_from_instance ((GTypeInstance *) node));
|
|
return gsk_gpu_get_node_as_image_via_offscreen (frame, flags, ccs, clip_bounds, scale, node, out_bounds);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_set_scissor (GskGpuNodeProcessor *self,
|
|
const cairo_rectangle_int_t *scissor)
|
|
{
|
|
graphene_rect_t clip_rect;
|
|
|
|
self->scissor = *scissor;
|
|
|
|
if (!gsk_gpu_node_processor_rect_device_to_clip (self,
|
|
&GSK_RECT_INIT_CAIRO (scissor),
|
|
&clip_rect))
|
|
{
|
|
/* If the transform is screwed up in the places where we call this function,
|
|
* something has seriously gone wrong.
|
|
*/
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
gsk_gpu_clip_init_empty (&self->clip, &clip_rect);
|
|
|
|
self->pending_globals |= GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR;
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_render (GskGpuNodeProcessor *self,
|
|
GskGpuImage *target,
|
|
cairo_region_t *clip,
|
|
GskRenderNode *node,
|
|
GskRenderPassType pass_type)
|
|
{
|
|
GskGpuFirstNodeInfo info = {
|
|
.target = target,
|
|
.pass_type = pass_type,
|
|
};
|
|
int i, n, best, best_size;
|
|
cairo_rectangle_int_t rect;
|
|
gboolean do_culling;
|
|
|
|
cairo_region_get_extents (clip, &info.extents);
|
|
info.whole_area = cairo_region_contains_rectangle (clip, &info.extents) == CAIRO_REGION_OVERLAP_IN;
|
|
info.min_occlusion_pixels = gsk_gpu_image_get_width (target) * gsk_gpu_image_get_height (target) *
|
|
MIN_PERCENTAGE_FOR_OCCLUSION_PASS / 100;
|
|
info.min_occlusion_pixels = MAX (info.min_occlusion_pixels, MIN_PIXELS_FOR_OCCLUSION_PASS);
|
|
|
|
do_culling = gsk_gpu_frame_should_optimize (self->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;
|
|
}
|
|
}
|
|
|
|
cairo_region_get_rectangle (clip, best, &rect);
|
|
|
|
if (best_size < MIN_PIXELS_FOR_OCCLUSION_PASS)
|
|
break;
|
|
|
|
gsk_gpu_node_processor_set_scissor (self, &rect);
|
|
|
|
if (!gsk_gpu_node_processor_add_first_node (self,
|
|
&info,
|
|
node))
|
|
{
|
|
gsk_gpu_first_node_begin_rendering (self, &info, GSK_VEC4_TRANSPARENT);
|
|
gsk_gpu_node_processor_add_node (self, node);
|
|
do_culling = FALSE;
|
|
}
|
|
else if (GSK_DEBUG_CHECK (OCCLUSION))
|
|
{
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
gsk_gpu_color_op (self->frame,
|
|
GSK_GPU_SHADER_CLIP_NONE,
|
|
self->ccs,
|
|
1.0,
|
|
&self->offset,
|
|
&GRAPHENE_RECT_INIT(0, 0, 10000, 10000),
|
|
&GDK_COLOR_SRGB (1.0, 1.0, 1.0, 0.6));
|
|
}
|
|
|
|
cairo_region_subtract_rectangle (clip, &self->scissor);
|
|
}
|
|
|
|
for (i = 0; i < cairo_region_num_rectangles (clip); i++)
|
|
{
|
|
cairo_region_get_rectangle (clip, i, &rect);
|
|
|
|
gsk_gpu_node_processor_set_scissor (self, &rect);
|
|
|
|
/* only run pass if it's covering the whole rectangle */
|
|
info.min_occlusion_pixels = rect.width * rect.height;
|
|
|
|
if (!gsk_gpu_node_processor_add_first_node (self,
|
|
&info,
|
|
node))
|
|
{
|
|
gsk_gpu_first_node_begin_rendering (self, &info, GSK_VEC4_TRANSPARENT);
|
|
gsk_gpu_node_processor_add_node (self, node);
|
|
}
|
|
}
|
|
|
|
if (info.has_started_rendering)
|
|
{
|
|
gsk_gpu_render_pass_end_op (self->frame,
|
|
target,
|
|
pass_type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_gpu_node_processor_convert_to (GskGpuNodeProcessor *self,
|
|
GskGpuImage *image,
|
|
GdkColorState *image_color_state,
|
|
const graphene_rect_t *rect,
|
|
const graphene_rect_t *tex_rect)
|
|
{
|
|
gsk_gpu_node_processor_sync_globals (self, 0);
|
|
|
|
if (!GDK_IS_DEFAULT_COLOR_STATE (self->ccs))
|
|
{
|
|
const GdkCicp *cicp = gdk_color_state_get_cicp (self->ccs);
|
|
|
|
g_assert (cicp != NULL);
|
|
|
|
gsk_gpu_convert_to_cicp_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, rect),
|
|
cicp,
|
|
gsk_gpu_color_states_create_cicp (image_color_state, TRUE, TRUE),
|
|
self->opacity,
|
|
FALSE,
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
rect,
|
|
tex_rect
|
|
});
|
|
}
|
|
else
|
|
{
|
|
gsk_gpu_convert_op (self->frame,
|
|
gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, rect),
|
|
gsk_gpu_node_processor_color_states_explicit (self, image_color_state, TRUE),
|
|
self->opacity,
|
|
FALSE,
|
|
&self->offset,
|
|
&(GskGpuShaderImage) {
|
|
image,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
rect,
|
|
tex_rect
|
|
});
|
|
}
|
|
}
|
|
|
|
void
|
|
gsk_gpu_node_processor_process (GskGpuFrame *frame,
|
|
GskGpuImage *target,
|
|
GdkColorState *target_color_state,
|
|
cairo_region_t *clip,
|
|
GskRenderNode *node,
|
|
const graphene_rect_t *viewport,
|
|
GskRenderPassType pass_type)
|
|
{
|
|
GskGpuNodeProcessor self;
|
|
GdkColorState *ccs;
|
|
GskGpuImage *image;
|
|
graphene_rect_t clip_bounds, tex_rect;
|
|
cairo_rectangle_int_t extents;
|
|
int i;
|
|
|
|
ccs = gdk_color_state_get_rendering_color_state (target_color_state);
|
|
|
|
cairo_region_get_extents (clip, &extents);
|
|
|
|
gsk_gpu_node_processor_init (&self,
|
|
frame,
|
|
target,
|
|
target_color_state,
|
|
&extents,
|
|
viewport);
|
|
|
|
if (gdk_color_state_equal (ccs, target_color_state))
|
|
{
|
|
gsk_gpu_node_processor_render (&self, target, clip, node, pass_type);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < cairo_region_num_rectangles (clip); i++)
|
|
{
|
|
cairo_rectangle_int_t rect;
|
|
|
|
cairo_region_get_rectangle (clip, i, &rect);
|
|
gsk_gpu_node_processor_set_scissor (&self, &rect);
|
|
|
|
/* Can't use gsk_gpu_node_processor_get_node_as_image () because of colorspaces */
|
|
if (!gsk_gpu_node_processor_clip_node_bounds (&self, node, &clip_bounds))
|
|
continue;
|
|
|
|
gsk_rect_snap_to_grid (&clip_bounds, &self.scale, &self.offset, &clip_bounds);
|
|
image = gsk_gpu_get_node_as_image (self.frame,
|
|
0,
|
|
ccs,
|
|
&clip_bounds,
|
|
&self.scale,
|
|
node,
|
|
&tex_rect);
|
|
if (image == NULL)
|
|
continue;
|
|
|
|
gsk_gpu_render_pass_begin_op (frame,
|
|
target,
|
|
&rect,
|
|
GSK_GPU_LOAD_OP_DONT_CARE,
|
|
NULL,
|
|
pass_type);
|
|
|
|
self.blend = GSK_GPU_BLEND_NONE;
|
|
self.pending_globals |= GSK_GPU_GLOBAL_BLEND;
|
|
|
|
gsk_gpu_node_processor_convert_to (&self,
|
|
image,
|
|
ccs,
|
|
&clip_bounds,
|
|
&tex_rect);
|
|
|
|
gsk_gpu_render_pass_end_op (frame,
|
|
target,
|
|
pass_type);
|
|
|
|
g_object_unref (image);
|
|
}
|
|
}
|
|
|
|
gsk_gpu_node_processor_finish (&self);
|
|
|
|
cairo_region_destroy (clip);
|
|
}
|
|
|
|
GskGpuImage *
|
|
gsk_gpu_node_processor_convert_image (GskGpuFrame *frame,
|
|
GdkMemoryFormat target_format,
|
|
GdkColorState *target_color_state,
|
|
GskGpuImage *image,
|
|
GdkColorState *image_color_state)
|
|
{
|
|
GskGpuNodeProcessor self;
|
|
GskGpuImage *converted, *intermediate = NULL;
|
|
gsize width, height;
|
|
|
|
width = gsk_gpu_image_get_width (image);
|
|
height = gsk_gpu_image_get_height (image);
|
|
|
|
converted = gsk_gpu_device_create_offscreen_image (gsk_gpu_frame_get_device (frame),
|
|
FALSE,
|
|
target_format,
|
|
gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_SRGB,
|
|
width,
|
|
height);
|
|
if (converted == NULL)
|
|
return NULL;
|
|
|
|
/* We need to go via an intermediate colorstate */
|
|
if (!GDK_IS_DEFAULT_COLOR_STATE (image_color_state) &&
|
|
!GDK_IS_DEFAULT_COLOR_STATE (target_color_state))
|
|
{
|
|
GdkColorState *ccs = gdk_color_state_get_rendering_color_state (image_color_state);
|
|
|
|
intermediate = gsk_gpu_copy_image (frame, ccs, image, image_color_state, FALSE);
|
|
if (intermediate == NULL)
|
|
return NULL;
|
|
image = intermediate;
|
|
image_color_state = ccs;
|
|
}
|
|
|
|
gsk_gpu_node_processor_init (&self,
|
|
frame,
|
|
converted,
|
|
target_color_state,
|
|
&(cairo_rectangle_int_t) { 0, 0, width, height },
|
|
&GRAPHENE_RECT_INIT (0, 0, width, height));
|
|
gsk_gpu_render_pass_begin_op (frame,
|
|
converted,
|
|
&(cairo_rectangle_int_t) { 0, 0, width, height },
|
|
GSK_GPU_LOAD_OP_DONT_CARE,
|
|
NULL,
|
|
GSK_RENDER_PASS_OFFSCREEN);
|
|
|
|
gsk_gpu_node_processor_sync_globals (&self, 0);
|
|
|
|
if (GDK_IS_DEFAULT_COLOR_STATE (target_color_state))
|
|
{
|
|
gsk_gpu_node_processor_image_op (&self,
|
|
image,
|
|
image_color_state,
|
|
GSK_GPU_SAMPLER_DEFAULT,
|
|
&GRAPHENE_RECT_INIT (0, 0, width, height),
|
|
&GRAPHENE_RECT_INIT (0, 0, width, height));
|
|
}
|
|
else
|
|
{
|
|
gsk_gpu_node_processor_convert_to (&self,
|
|
image,
|
|
image_color_state,
|
|
&GRAPHENE_RECT_INIT (0, 0, width, height),
|
|
&GRAPHENE_RECT_INIT (0, 0, width, height));
|
|
}
|
|
|
|
gsk_gpu_render_pass_end_op (self.frame,
|
|
converted,
|
|
GSK_RENDER_PASS_OFFSCREEN);
|
|
gsk_gpu_node_processor_finish (&self);
|
|
|
|
g_clear_object (&intermediate);
|
|
|
|
return converted;
|
|
}
|