gtk/gsk/gl/gskglrenderops.c
Alexander Larsson 51ab56d33a gl: Track the current uniform state for custom programs
This allows us to avoid updating uniforms if that is not necessary. This
in turn allows us to sometimes reuse the same draw op by just extending the
vertex array size we draw rather than doing a separate glDraw call.

For example, in the fishbowl demo, all the icons added at the same
time will have the same time and size, so we emit single draw calls
with 100s of triangles instead of 100s of draw calls with 2 triangles.
2020-09-29 11:52:39 +02:00

1041 lines
29 KiB
C

#include "gskglrenderopsprivate.h"
#include "gsktransform.h"
typedef struct
{
GskRoundedRect rect;
bool is_rectilinear;
} ClipStackEntry;
static inline gboolean
rect_equal (const graphene_rect_t *a,
const graphene_rect_t *b)
{
return memcmp (a, b, sizeof (graphene_rect_t)) == 0;
}
static inline gboolean G_GNUC_PURE
rounded_rect_equal (const GskRoundedRect *r1,
const GskRoundedRect *r2)
{
int i;
if (!r1)
return FALSE;
if (r1->bounds.origin.x != r2->bounds.origin.x ||
r1->bounds.origin.y != r2->bounds.origin.y ||
r1->bounds.size.width != r2->bounds.size.width ||
r1->bounds.size.height != r2->bounds.size.height)
return FALSE;
for (i = 0; i < 4; i ++)
if (r1->corner[i].width != r2->corner[i].width ||
r1->corner[i].height != r2->corner[i].height)
return FALSE;
return TRUE;
}
static inline gboolean G_GNUC_PURE
rounded_rect_corners_equal (const GskRoundedRect *r1,
const GskRoundedRect *r2)
{
int i;
if (!r1)
return FALSE;
for (i = 0; i < 4; i ++)
if (r1->corner[i].width != r2->corner[i].width ||
r1->corner[i].height != r2->corner[i].height)
return FALSE;
return TRUE;
}
static inline ProgramState *
get_current_program_state (RenderOpBuilder *builder)
{
if (!builder->current_program)
return NULL;
return &builder->current_program->state;
}
void
ops_finish (RenderOpBuilder *builder)
{
if (builder->mv_stack)
g_array_free (builder->mv_stack, TRUE);
builder->mv_stack = NULL;
if (builder->clip_stack)
g_array_free (builder->clip_stack, TRUE);
builder->clip_stack = NULL;
builder->dx = 0;
builder->dy = 0;
builder->scale_x = 1;
builder->scale_y = 1;
builder->current_modelview = NULL;
builder->current_clip = NULL;
builder->clip_is_rectilinear = TRUE;
builder->current_render_target = 0;
builder->current_texture = 0;
builder->current_program = NULL;
graphene_matrix_init_identity (&builder->current_projection);
builder->current_viewport = GRAPHENE_RECT_INIT (0, 0, 0, 0);
}
/* Debugging only! */
void
ops_dump_framebuffer (RenderOpBuilder *builder,
const char *filename,
int width,
int height)
{
OpDumpFrameBuffer *op;
op = ops_begin (builder, OP_DUMP_FRAMEBUFFER);
op->filename = g_strdup (filename);
op->width = width;
op->height = height;
}
void
ops_push_debug_group (RenderOpBuilder *builder,
const char *text)
{
OpDebugGroup *op;
op = ops_begin (builder, OP_PUSH_DEBUG_GROUP);
strncpy (op->text, text, sizeof(op->text) - 1);
op->text[sizeof(op->text) - 1] = 0; /* Ensure zero terminated */
}
void
ops_pop_debug_group (RenderOpBuilder *builder)
{
ops_begin (builder, OP_POP_DEBUG_GROUP);
}
float
ops_get_scale (const RenderOpBuilder *builder)
{
g_assert (builder->mv_stack != NULL);
g_assert (builder->mv_stack->len >= 1);
/* TODO: Use two separate values */
return MAX (builder->scale_x, builder->scale_y);
}
static void
extract_matrix_metadata (GskTransform *transform,
OpsMatrixMetadata *md)
{
float dummy;
switch (gsk_transform_get_category (transform))
{
case GSK_TRANSFORM_CATEGORY_IDENTITY:
case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
md->scale_x = 1;
md->scale_y = 1;
break;
case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
gsk_transform_to_affine (transform,
&md->scale_x, &md->scale_y,
&dummy, &dummy);
break;
case GSK_TRANSFORM_CATEGORY_UNKNOWN:
case GSK_TRANSFORM_CATEGORY_ANY:
case GSK_TRANSFORM_CATEGORY_3D:
case GSK_TRANSFORM_CATEGORY_2D:
{
graphene_vec3_t col1;
graphene_vec3_t col2;
graphene_matrix_t m;
gsk_transform_to_matrix (transform, &m);
/* TODO: 90% sure this is incorrect. But we should never hit this code
* path anyway. */
graphene_vec3_init (&col1,
graphene_matrix_get_value (&m, 0, 0),
graphene_matrix_get_value (&m, 1, 0),
graphene_matrix_get_value (&m, 2, 0));
graphene_vec3_init (&col2,
graphene_matrix_get_value (&m, 0, 1),
graphene_matrix_get_value (&m, 1, 1),
graphene_matrix_get_value (&m, 2, 1));
md->scale_x = graphene_vec3_length (&col1);
md->scale_y = graphene_vec3_length (&col2);
}
break;
default:
{}
}
}
void
ops_transform_bounds_modelview (const RenderOpBuilder *builder,
const graphene_rect_t *src,
graphene_rect_t *dst)
{
graphene_rect_t r = *src;
g_assert (builder->mv_stack != NULL);
g_assert (builder->mv_stack->len >= 1);
r.origin.x += builder->dx;
r.origin.y += builder->dy;
gsk_transform_transform_bounds (builder->current_modelview, &r, dst);
}
void
ops_init (RenderOpBuilder *builder)
{
memset (builder, 0, sizeof (*builder));
builder->current_opacity = 1.0f;
op_buffer_init (&builder->render_ops);
builder->vertices = g_array_new (FALSE, TRUE, sizeof (GskQuadVertex));
}
void
ops_free (RenderOpBuilder *builder)
{
g_array_unref (builder->vertices);
op_buffer_destroy (&builder->render_ops);
}
void
ops_set_program (RenderOpBuilder *builder,
Program *program)
{
OpProgram *op;
ProgramState *program_state = NULL;
if (builder->current_program == program)
return;
op = ops_begin (builder, OP_CHANGE_PROGRAM);
op->program = program;
builder->current_program = program;
program_state = &program->state;
if (memcmp (&builder->current_projection, &program_state->projection, sizeof (graphene_matrix_t)) != 0)
{
OpMatrix *opm;
opm = ops_begin (builder, OP_CHANGE_PROJECTION);
opm->matrix = builder->current_projection;
program_state->projection = builder->current_projection;
}
if (program_state->modelview == NULL ||
!gsk_transform_equal (builder->current_modelview, program_state->modelview))
{
OpMatrix *opm;
opm = ops_begin (builder, OP_CHANGE_MODELVIEW);
gsk_transform_to_matrix (builder->current_modelview, &opm->matrix);
gsk_transform_unref (program_state->modelview);
program_state->modelview = gsk_transform_ref (builder->current_modelview);
}
if (!rect_equal (&builder->current_viewport, &program_state->viewport))
{
OpViewport *opv;
opv = ops_begin (builder, OP_CHANGE_VIEWPORT);
opv->viewport = builder->current_viewport;
program_state->viewport = builder->current_viewport;
}
if (!rounded_rect_equal (builder->current_clip, &program_state->clip))
{
OpClip *opc;
opc = ops_begin (builder, OP_CHANGE_CLIP);
opc->clip = *builder->current_clip;
opc->send_corners = !rounded_rect_corners_equal (builder->current_clip, &program_state->clip);
program_state->clip = *builder->current_clip;
}
if (program_state->opacity != builder->current_opacity)
{
OpOpacity *opo;
opo = ops_begin (builder, OP_CHANGE_OPACITY);
opo->opacity = builder->current_opacity;
program_state->opacity = builder->current_opacity;
}
}
static void
ops_set_clip (RenderOpBuilder *builder,
const GskRoundedRect *clip)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpClip *op;
if (current_program_state &&
rounded_rect_equal (&current_program_state->clip, clip))
return;
if (!(op = op_buffer_peek_tail_checked (&builder->render_ops, OP_CHANGE_CLIP)))
{
op = op_buffer_add (&builder->render_ops, OP_CHANGE_CLIP);
op->send_corners = !current_program_state ||
!rounded_rect_corners_equal (&current_program_state->clip, clip);
}
else
{
/* If the op before sent the corners, this one needs, too */
op->send_corners |= !current_program_state ||
!rounded_rect_corners_equal (&current_program_state->clip, clip);
}
op->clip = *clip;
if (current_program_state)
current_program_state->clip = *clip;
}
void
ops_push_clip (RenderOpBuilder *self,
const GskRoundedRect *clip)
{
ClipStackEntry entry;
if (G_UNLIKELY (self->clip_stack == NULL))
self->clip_stack = g_array_new (FALSE, TRUE, sizeof (ClipStackEntry));
g_assert (self->clip_stack != NULL);
entry.rect = *clip;
entry.is_rectilinear = gsk_rounded_rect_is_rectilinear (clip);
g_array_append_val (self->clip_stack, entry);
self->current_clip = &g_array_index (self->clip_stack, ClipStackEntry, self->clip_stack->len - 1).rect;
self->clip_is_rectilinear = entry.is_rectilinear;
ops_set_clip (self, clip);
}
void
ops_pop_clip (RenderOpBuilder *self)
{
const ClipStackEntry *head;
g_assert (self->clip_stack);
g_assert (self->clip_stack->len >= 1);
self->clip_stack->len --;
head = &g_array_index (self->clip_stack, ClipStackEntry, self->clip_stack->len - 1);
if (self->clip_stack->len >= 1)
{
self->current_clip = &head->rect;
self->clip_is_rectilinear = head->is_rectilinear;
ops_set_clip (self, &head->rect);
}
else
{
self->current_clip = NULL;
self->clip_is_rectilinear = TRUE;
}
}
gboolean
ops_has_clip (RenderOpBuilder *self)
{
return self->clip_stack != NULL &&
self->clip_stack->len > 1;
}
static void
ops_set_modelview_internal (RenderOpBuilder *builder,
GskTransform *transform)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpMatrix *op;
#if 0
XXX This is not possible if we want pop() to work.
if (builder->current_program &&
gsk_transform_equal (builder->current_program_state->modelview, transform))
return;
#endif
if (!(op = op_buffer_peek_tail_checked (&builder->render_ops, OP_CHANGE_MODELVIEW)))
op = op_buffer_add (&builder->render_ops, OP_CHANGE_MODELVIEW);
gsk_transform_to_matrix (transform, &op->matrix);
if (builder->current_program != NULL)
{
gsk_transform_unref (current_program_state->modelview);
current_program_state->modelview = gsk_transform_ref (transform);
}
}
/**
* ops_set_modelview:
* @builder
* @transform: (transfer full): The new modelview transform
*
* This sets the modelview to the given one without looking at the
* one that's currently set */
void
ops_set_modelview (RenderOpBuilder *builder,
GskTransform *transform)
{
MatrixStackEntry *entry;
if (G_UNLIKELY (builder->mv_stack == NULL))
builder->mv_stack = g_array_new (FALSE, TRUE, sizeof (MatrixStackEntry));
g_assert (builder->mv_stack != NULL);
g_array_set_size (builder->mv_stack, builder->mv_stack->len + 1);
entry = &g_array_index (builder->mv_stack, MatrixStackEntry, builder->mv_stack->len - 1);
entry->transform = transform;
entry->metadata.dx_before = builder->dx;
entry->metadata.dy_before = builder->dy;
extract_matrix_metadata (entry->transform, &entry->metadata);
builder->dx = 0;
builder->dy = 0;
builder->current_modelview = entry->transform;
builder->scale_x = entry->metadata.scale_x;
builder->scale_y = entry->metadata.scale_y;
ops_set_modelview_internal (builder, entry->transform);
}
/* This sets the given modelview to the one we get when multiplying
* the given modelview with the current one. */
void
ops_push_modelview (RenderOpBuilder *builder,
GskTransform *transform)
{
MatrixStackEntry *entry;
if (G_UNLIKELY (builder->mv_stack == NULL))
builder->mv_stack = g_array_new (FALSE, TRUE, sizeof (MatrixStackEntry));
g_assert (builder->mv_stack != NULL);
g_array_set_size (builder->mv_stack, builder->mv_stack->len + 1);
entry = &g_array_index (builder->mv_stack, MatrixStackEntry, builder->mv_stack->len - 1);
if (G_LIKELY (builder->mv_stack->len >= 2))
{
const MatrixStackEntry *cur;
GskTransform *t = NULL;
cur = &g_array_index (builder->mv_stack, MatrixStackEntry, builder->mv_stack->len - 2);
/* Multiply given matrix with current modelview */
t = gsk_transform_translate (gsk_transform_ref (cur->transform),
&(graphene_point_t) { builder->dx, builder->dy});
t = gsk_transform_transform (t, transform);
entry->transform = t;
}
else
{
entry->transform = gsk_transform_ref (transform);
}
entry->metadata.dx_before = builder->dx;
entry->metadata.dy_before = builder->dy;
extract_matrix_metadata (entry->transform, &entry->metadata);
builder->dx = 0;
builder->dy = 0;
builder->scale_x = entry->metadata.scale_x;
builder->scale_y = entry->metadata.scale_y;
builder->current_modelview = entry->transform;
ops_set_modelview_internal (builder, entry->transform);
}
void
ops_pop_modelview (RenderOpBuilder *builder)
{
const MatrixStackEntry *head;
g_assert (builder->mv_stack);
g_assert (builder->mv_stack->len >= 1);
head = &g_array_index (builder->mv_stack, MatrixStackEntry, builder->mv_stack->len - 1);
builder->dx = head->metadata.dx_before;
builder->dy = head->metadata.dy_before;
gsk_transform_unref (head->transform);
builder->mv_stack->len --;
head = &g_array_index (builder->mv_stack, MatrixStackEntry, builder->mv_stack->len - 1);
if (builder->mv_stack->len >= 1)
{
builder->scale_x = head->metadata.scale_x;
builder->scale_y = head->metadata.scale_y;
builder->current_modelview = head->transform;
ops_set_modelview_internal (builder, head->transform);
}
else
{
builder->current_modelview = NULL;
}
}
graphene_matrix_t
ops_set_projection (RenderOpBuilder *builder,
const graphene_matrix_t *projection)
{
ProgramState *current_program_state = get_current_program_state (builder);
graphene_matrix_t prev_mv;
OpMatrix *op;
if (!(op = op_buffer_peek_tail_checked (&builder->render_ops, OP_CHANGE_PROJECTION)))
op = op_buffer_add (&builder->render_ops, OP_CHANGE_PROJECTION);
op->matrix = *projection;
if (builder->current_program != NULL)
current_program_state->projection = *projection;
prev_mv = builder->current_projection;
builder->current_projection = *projection;
return prev_mv;
}
graphene_rect_t
ops_set_viewport (RenderOpBuilder *builder,
const graphene_rect_t *viewport)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpViewport *op;
graphene_rect_t prev_viewport;
if (current_program_state != NULL &&
rect_equal (&current_program_state->viewport, viewport))
return current_program_state->viewport;
op = ops_begin (builder, OP_CHANGE_VIEWPORT);
op->viewport = *viewport;
if (current_program_state != NULL)
current_program_state->viewport = *viewport;
prev_viewport = builder->current_viewport;
builder->current_viewport = *viewport;
return prev_viewport;
}
void
ops_set_texture (RenderOpBuilder *builder,
int texture_id)
{
OpTexture *op;
if (builder->current_texture == texture_id)
return;
op = ops_begin (builder, OP_CHANGE_SOURCE_TEXTURE);
op->texture_id = texture_id;
builder->current_texture = texture_id;
}
void
ops_set_extra_texture (RenderOpBuilder *builder,
int texture_id,
int idx)
{
OpExtraTexture *op;
op = ops_begin (builder, OP_CHANGE_EXTRA_SOURCE_TEXTURE);
op->texture_id = texture_id;
op->idx = idx;
}
int
ops_set_render_target (RenderOpBuilder *builder,
int render_target_id)
{
OpRenderTarget *op;
int prev_render_target;
if (builder->current_render_target == render_target_id)
return render_target_id;
prev_render_target = builder->current_render_target;
if (!(op = op_buffer_peek_tail_checked (&builder->render_ops, OP_CHANGE_RENDER_TARGET)))
op = op_buffer_add (&builder->render_ops, OP_CHANGE_RENDER_TARGET);
op->render_target_id = render_target_id;
builder->current_render_target = render_target_id;
return prev_render_target;
}
float
ops_set_opacity (RenderOpBuilder *builder,
float opacity)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpOpacity *op;
float prev_opacity;
if (builder->current_opacity == opacity)
return opacity;
if (!(op = op_buffer_peek_tail_checked (&builder->render_ops, OP_CHANGE_OPACITY)))
op = op_buffer_add (&builder->render_ops, OP_CHANGE_OPACITY);
op->opacity = opacity;
prev_opacity = builder->current_opacity;
builder->current_opacity = opacity;
if (builder->current_program != NULL)
current_program_state->opacity = opacity;
return prev_opacity;
}
void
ops_set_color (RenderOpBuilder *builder,
const GdkRGBA *color)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpColor *op;
if (gdk_rgba_equal (color, &current_program_state->color))
return;
current_program_state->color = *color;
op = ops_begin (builder, OP_CHANGE_COLOR);
op->rgba = color;
}
void
ops_set_gl_shader_args (RenderOpBuilder *builder,
GskGLShader *shader,
float width,
float height,
const guchar *uniform_data)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpGLShader *op;
gsize args_size = gsk_gl_shader_get_args_size (shader);
if (current_program_state)
{
if (current_program_state->gl_shader.width == width &&
current_program_state->gl_shader.height == height &&
current_program_state->gl_shader.uniform_data_len == args_size &&
memcmp (current_program_state->gl_shader.uniform_data, uniform_data, args_size) == 0)
return;
current_program_state->gl_shader.width = width;
current_program_state->gl_shader.height = height;
if (args_size > sizeof (current_program_state->gl_shader.uniform_data))
current_program_state->gl_shader.uniform_data_len = 0;
else
{
current_program_state->gl_shader.uniform_data_len = args_size;
memcpy (current_program_state->gl_shader.uniform_data, uniform_data, args_size);
}
}
op = ops_begin (builder, OP_CHANGE_GL_SHADER_ARGS);
op->shader = shader;
op->size[0] = width;
op->size[1] = height;
op->uniform_data = uniform_data;
}
void
ops_set_color_matrix (RenderOpBuilder *builder,
const graphene_matrix_t *matrix,
const graphene_vec4_t *offset)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpColorMatrix *op;
bool offset_equal;
offset_equal = memcmp (offset,
&current_program_state->color_matrix.offset,
sizeof (graphene_vec4_t)) == 0;
if (memcmp (matrix,
&current_program_state->color_matrix.matrix,
sizeof (graphene_matrix_t)) == 0 &&
offset_equal)
return;
current_program_state->color_matrix.matrix = *matrix;
op = ops_begin (builder, OP_CHANGE_COLOR_MATRIX);
op->matrix = matrix;
if (!offset_equal)
{
op->offset.value = offset;
op->offset.send = TRUE;
current_program_state->color_matrix.offset = *offset;
}
else
op->offset.send = FALSE;
}
void
ops_set_border (RenderOpBuilder *builder,
const GskRoundedRect *outline)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpBorder *op;
if (memcmp (&current_program_state->border.outline,
outline, sizeof (GskRoundedRect)) == 0)
return;
current_program_state->border.outline = *outline;
op = ops_begin (builder, OP_CHANGE_BORDER);
op->outline = *outline;
}
void
ops_set_border_width (RenderOpBuilder *builder,
const float *widths)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpBorder *op;
g_assert (current_program_state);
if (memcmp (current_program_state->border.widths,
widths, sizeof (float) * 4) == 0)
return;
memcpy (&current_program_state->border.widths,
widths, sizeof (float) * 4);
op = ops_begin (builder, OP_CHANGE_BORDER_WIDTH);
op->widths[0] = widths[0];
op->widths[1] = widths[1];
op->widths[2] = widths[2];
op->widths[3] = widths[3];
}
void
ops_set_border_color (RenderOpBuilder *builder,
const GdkRGBA *color)
{
ProgramState *current_program_state = get_current_program_state (builder);
OpBorder *op;
if (gdk_rgba_equal (color, &current_program_state->border.color))
return;
op = op_buffer_add (&builder->render_ops, OP_CHANGE_BORDER_COLOR);
op->color = color;
current_program_state->border.color = *color;
}
GskQuadVertex *
ops_draw (RenderOpBuilder *builder,
const GskQuadVertex vertex_data[GL_N_VERTICES])
{
OpDraw *op;
if ((op = op_buffer_peek_tail_checked (&builder->render_ops, OP_DRAW)))
{
op->vao_size += GL_N_VERTICES;
}
else
{
op = op_buffer_add (&builder->render_ops, OP_DRAW);
op->vao_offset = builder->vertices->len;
op->vao_size = GL_N_VERTICES;
}
if (vertex_data)
{
g_array_append_vals (builder->vertices, vertex_data, GL_N_VERTICES);
return NULL; /* Better not use this on the caller side */
}
g_array_set_size (builder->vertices, builder->vertices->len + GL_N_VERTICES);
return &g_array_index (builder->vertices, GskQuadVertex, builder->vertices->len - GL_N_VERTICES);
}
/* The offset is only valid for the current modelview.
* Setting a new modelview will add the offset to that matrix
* and reset the internal offset to 0. */
void
ops_offset (RenderOpBuilder *builder,
float x,
float y)
{
builder->dx += x;
builder->dy += y;
}
gpointer
ops_begin (RenderOpBuilder *builder,
OpKind kind)
{
return op_buffer_add (&builder->render_ops, kind);
}
void
ops_reset (RenderOpBuilder *builder)
{
op_buffer_clear (&builder->render_ops);
g_array_set_size (builder->vertices, 0);
}
OpBuffer *
ops_get_buffer (RenderOpBuilder *builder)
{
return &builder->render_ops;
}
void
ops_set_inset_shadow (RenderOpBuilder *self,
const GskRoundedRect outline,
float spread,
const GdkRGBA *color,
float dx,
float dy)
{
ProgramState *current_program_state = get_current_program_state (self);
OpShadow *op;
g_assert (current_program_state);
op = ops_begin (self, OP_CHANGE_INSET_SHADOW);
if (!rounded_rect_equal (&outline, &current_program_state->inset_shadow.outline))
{
op->outline.value = outline;
op->outline.send = TRUE;
op->outline.send_corners = !rounded_rect_corners_equal (&current_program_state->inset_shadow.outline,
&outline);
current_program_state->inset_shadow.outline = outline;
}
else
op->outline.send = FALSE;
if (spread != current_program_state->inset_shadow.spread)
{
op->spread.value = spread;
op->spread.send = TRUE;
current_program_state->inset_shadow.spread = spread;
}
else
op->spread.send = FALSE;
if (!gdk_rgba_equal (color, &current_program_state->inset_shadow.color))
{
op->color.value = color;
op->color.send = TRUE;
current_program_state->inset_shadow.color = *color;
}
else
op->color.send = FALSE;
if (dx != current_program_state->inset_shadow.dx ||
dy != current_program_state->inset_shadow.dy)
{
op->offset.value[0] = dx;
op->offset.value[1] = dy;
op->offset.send = TRUE;
current_program_state->inset_shadow.dx = dx;
current_program_state->inset_shadow.dy = dy;
}
else
op->offset.send = FALSE;
if (!op->outline.send &&
!op->spread.send &&
!op->offset.send &&
!op->color.send)
{
op_buffer_pop_tail (&self->render_ops);
}
}
void
ops_set_unblurred_outset_shadow (RenderOpBuilder *self,
const GskRoundedRect outline,
float spread,
const GdkRGBA *color,
float dx,
float dy)
{
ProgramState *current_program_state = get_current_program_state (self);
OpShadow *op;
g_assert (current_program_state);
op = ops_begin (self, OP_CHANGE_UNBLURRED_OUTSET_SHADOW);
if (!rounded_rect_equal (&outline, &current_program_state->unblurred_outset_shadow.outline))
{
op->outline.value = outline;
op->outline.send = TRUE;
op->outline.send_corners = !rounded_rect_corners_equal (&current_program_state->unblurred_outset_shadow.outline,
&outline);
current_program_state->unblurred_outset_shadow.outline = outline;
}
else
op->outline.send = FALSE;
if (spread != current_program_state->unblurred_outset_shadow.spread)
{
op->spread.value = spread;
op->spread.send = TRUE;
current_program_state->unblurred_outset_shadow.spread = spread;
}
else
op->spread.send = FALSE;
if (!gdk_rgba_equal (color, &current_program_state->unblurred_outset_shadow.color))
{
op->color.value = color;
op->color.send = TRUE;
current_program_state->unblurred_outset_shadow.color = *color;
}
else
op->color.send = FALSE;
if (dx != current_program_state->unblurred_outset_shadow.dx ||
dy != current_program_state->unblurred_outset_shadow.dy)
{
op->offset.value[0] = dx;
op->offset.value[1] = dy;
op->offset.send = TRUE;
current_program_state->unblurred_outset_shadow.dx = dx;
current_program_state->unblurred_outset_shadow.dy = dy;
}
else
op->offset.send = FALSE;
}
void
ops_set_linear_gradient (RenderOpBuilder *self,
guint n_color_stops,
const GskColorStop *color_stops,
float start_x,
float start_y,
float end_x,
float end_y)
{
ProgramState *current_program_state = get_current_program_state (self);
OpLinearGradient *op;
const guint real_n_color_stops = MIN (GL_MAX_GRADIENT_STOPS, n_color_stops);
g_assert (current_program_state);
op = ops_begin (self, OP_CHANGE_LINEAR_GRADIENT);
/* We always save the n_color_stops value in the op so the renderer can use it in
* cases where we send the color stops, but not n_color_stops */
op->n_color_stops.value = real_n_color_stops;
if (current_program_state->linear_gradient.n_color_stops != real_n_color_stops)
{
op->n_color_stops.send = TRUE;
current_program_state->linear_gradient.n_color_stops = real_n_color_stops;
}
else
op->n_color_stops.send = FALSE;
op->color_stops.send = FALSE;
if (!op->n_color_stops.send)
{
g_assert (current_program_state->linear_gradient.n_color_stops == real_n_color_stops);
for (guint i = 0; i < real_n_color_stops; i ++)
{
const GskColorStop *s1 = &color_stops[i];
const GskColorStop *s2 = &current_program_state->linear_gradient.color_stops[i];
if (s1->offset != s2->offset ||
!gdk_rgba_equal (&s1->color, &s2->color))
{
op->color_stops.send = TRUE;
break;
}
}
}
else
op->color_stops.send = TRUE;
if (op->color_stops.send)
{
op->color_stops.value = color_stops;
memcpy (&current_program_state->linear_gradient.color_stops,
color_stops,
sizeof (GskColorStop) * real_n_color_stops);
}
op->start_point[0] = start_x;
op->start_point[1] = start_y;
op->end_point[0] = end_x;
op->end_point[1] = end_y;
}
void
ops_set_radial_gradient (RenderOpBuilder *self,
guint n_color_stops,
const GskColorStop *color_stops,
float center_x,
float center_y,
float start,
float end,
float hradius,
float vradius)
{
const guint real_n_color_stops = MIN (GL_MAX_GRADIENT_STOPS, n_color_stops);
OpRadialGradient *op;
/* TODO: State tracking? */
op = ops_begin (self, OP_CHANGE_RADIAL_GRADIENT);
op->n_color_stops.value = real_n_color_stops;
op->n_color_stops.send = true;
op->color_stops.value = color_stops;
op->color_stops.send = true;
op->center[0] = center_x;
op->center[1] = center_y;
op->radius[0] = hradius;
op->radius[1] = vradius;
op->start = start;
op->end = end;
}