forked from AuroraMiddleware/gtk
12378f0afa
Instead of getting the translation x/y everytime we use the modelview, get it once, when extracting the metadata. Do the same with the scale. And save if the matrix is "simple" at all, i.e. if it only consists of a translation and/or scale. This will be helpful later when we start drawing transformed nodes on textures.
582 lines
17 KiB
C
582 lines
17 KiB
C
#include "gskglrenderopsprivate.h"
|
|
|
|
void
|
|
ops_finish (RenderOpBuilder *builder)
|
|
{
|
|
if (builder->mv_stack)
|
|
g_array_free (builder->mv_stack, TRUE);
|
|
}
|
|
|
|
static inline void
|
|
rgba_to_float (const GdkRGBA *c,
|
|
float *f)
|
|
{
|
|
f[0] = c->red;
|
|
f[1] = c->green;
|
|
f[2] = c->blue;
|
|
f[3] = c->alpha;
|
|
}
|
|
|
|
float
|
|
ops_get_scale (const RenderOpBuilder *builder)
|
|
{
|
|
const MatrixStackEntry *head;
|
|
|
|
g_assert (builder->mv_stack != NULL);
|
|
g_assert (builder->mv_stack->len >= 1);
|
|
|
|
head = &g_array_index (builder->mv_stack, MatrixStackEntry, builder->mv_stack->len - 1);
|
|
|
|
/* TODO: Use two separate values */
|
|
return MAX (head->metadata.scale_x,
|
|
head->metadata.scale_y);
|
|
}
|
|
|
|
static void
|
|
extract_matrix_metadata (const graphene_matrix_t *m,
|
|
OpsMatrixMetadata *md)
|
|
{
|
|
graphene_vec3_t col1;
|
|
graphene_vec3_t col2;
|
|
|
|
/* Translate */
|
|
md->translate_x = graphene_matrix_get_value (m, 3, 0);
|
|
md->translate_y = graphene_matrix_get_value (m, 3, 1);
|
|
|
|
/* Scale */
|
|
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);
|
|
|
|
/* A simple matrix (in our case) is one that doesn't do anything but scale
|
|
* and/or translate.
|
|
*
|
|
* For orher matrices, we fall back to offscreen drawing.
|
|
*/
|
|
md->simple = TRUE;
|
|
{
|
|
static const guchar check_zero[4][4] = {
|
|
{ 0, 1, 0, 1 }, /* If any of the values marked as '1' here is non-zero, */
|
|
{ 1, 0, 0, 1 }, /* We have to resort to offscreen drawing later on. */
|
|
{ 1, 1, 0, 1 },
|
|
{ 0, 0, 0, 0 },
|
|
};
|
|
int x, y;
|
|
|
|
for (x = 0; x < 4; x ++)
|
|
for (y = 0; y < 4; y ++)
|
|
if (check_zero[y][x] &&
|
|
graphene_matrix_get_value (m, y, x) != 0.0f)
|
|
{
|
|
md->simple = FALSE;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
md->only_translation = (md->simple && md->scale_x == 0 && md->scale_y == 0);
|
|
}
|
|
|
|
|
|
void
|
|
ops_transform_bounds_modelview (const RenderOpBuilder *builder,
|
|
const graphene_rect_t *src,
|
|
graphene_rect_t *dst)
|
|
{
|
|
const MatrixStackEntry *head;
|
|
|
|
g_assert (builder->mv_stack != NULL);
|
|
g_assert (builder->mv_stack->len >= 1);
|
|
|
|
head = &g_array_index (builder->mv_stack, MatrixStackEntry, builder->mv_stack->len - 1);
|
|
|
|
if (head->metadata.only_translation)
|
|
{
|
|
*dst = *src;
|
|
graphene_rect_offset (dst,
|
|
head->metadata.translate_x,
|
|
head->metadata.translate_y);
|
|
}
|
|
else
|
|
{
|
|
graphene_matrix_transform_bounds (builder->current_modelview,
|
|
src,
|
|
dst);
|
|
}
|
|
|
|
graphene_rect_offset (dst, builder->dx, builder->dy);
|
|
}
|
|
|
|
void
|
|
ops_set_program (RenderOpBuilder *builder,
|
|
const Program *program)
|
|
{
|
|
/* The tricky part about this is that we want to initialize all uniforms of a program
|
|
* to the current value from the builder, but only once. */
|
|
static const GskRoundedRect empty_clip;
|
|
static const graphene_matrix_t empty_matrix;
|
|
static const graphene_rect_t empty_rect;
|
|
RenderOp op;
|
|
|
|
if (builder->current_program == program)
|
|
return;
|
|
|
|
op.op = OP_CHANGE_PROGRAM;
|
|
op.program = program;
|
|
g_array_append_val (builder->render_ops, op);
|
|
builder->current_program = program;
|
|
|
|
/* If the projection is not yet set for this program, we use the current one. */
|
|
if (memcmp (&empty_matrix, &builder->program_state[program->index].projection, sizeof (graphene_matrix_t)) == 0 ||
|
|
memcmp (&builder->current_projection, &builder->program_state[program->index].projection, sizeof (graphene_matrix_t)) != 0)
|
|
{
|
|
op.op = OP_CHANGE_PROJECTION;
|
|
op.projection = builder->current_projection;
|
|
g_array_append_val (builder->render_ops, op);
|
|
builder->program_state[program->index].projection = builder->current_projection;
|
|
}
|
|
|
|
if (memcmp (&empty_matrix, &builder->program_state[program->index].modelview, sizeof (graphene_matrix_t)) == 0 ||
|
|
memcmp (builder->current_modelview, &builder->program_state[program->index].modelview, sizeof (graphene_matrix_t)) != 0)
|
|
{
|
|
op.op = OP_CHANGE_MODELVIEW;
|
|
op.modelview = *builder->current_modelview;
|
|
g_array_append_val (builder->render_ops, op);
|
|
builder->program_state[program->index].modelview = *builder->current_modelview;
|
|
}
|
|
|
|
if (memcmp (&empty_rect, &builder->program_state[program->index].viewport, sizeof (graphene_rect_t)) == 0 ||
|
|
memcmp (&builder->current_viewport, &builder->program_state[program->index].viewport, sizeof (graphene_rect_t)) != 0)
|
|
{
|
|
op.op = OP_CHANGE_VIEWPORT;
|
|
op.viewport = builder->current_viewport;
|
|
g_array_append_val (builder->render_ops, op);
|
|
builder->program_state[program->index].viewport = builder->current_viewport;
|
|
}
|
|
|
|
if (memcmp (&empty_clip, &builder->program_state[program->index].clip, sizeof (GskRoundedRect)) == 0 ||
|
|
memcmp (&builder->current_clip, &builder->program_state[program->index].clip, sizeof (GskRoundedRect)) != 0)
|
|
{
|
|
op.op = OP_CHANGE_CLIP;
|
|
op.clip = builder->current_clip;
|
|
g_array_append_val (builder->render_ops, op);
|
|
builder->program_state[program->index].clip = builder->current_clip;
|
|
}
|
|
|
|
if (builder->program_state[program->index].opacity != builder->current_opacity)
|
|
{
|
|
op.op = OP_CHANGE_OPACITY;
|
|
op.opacity = builder->current_opacity;
|
|
g_array_append_val (builder->render_ops, op);
|
|
builder->program_state[program->index].opacity = builder->current_opacity;
|
|
}
|
|
}
|
|
|
|
GskRoundedRect
|
|
ops_set_clip (RenderOpBuilder *builder,
|
|
const GskRoundedRect *clip)
|
|
{
|
|
RenderOp *last_op;
|
|
GskRoundedRect prev_clip;
|
|
|
|
if (builder->render_ops->len > 0)
|
|
{
|
|
last_op = &g_array_index (builder->render_ops, RenderOp, builder->render_ops->len - 1);
|
|
|
|
if (last_op->op == OP_CHANGE_CLIP)
|
|
{
|
|
last_op->clip = *clip;
|
|
}
|
|
else
|
|
{
|
|
RenderOp op;
|
|
|
|
op.op = OP_CHANGE_CLIP;
|
|
op.clip = *clip;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
}
|
|
|
|
if (builder->current_program != NULL)
|
|
builder->program_state[builder->current_program->index].clip = *clip;
|
|
|
|
prev_clip = builder->current_clip;
|
|
builder->current_clip = *clip;
|
|
|
|
return prev_clip;
|
|
}
|
|
|
|
static void
|
|
ops_set_modelview (RenderOpBuilder *builder,
|
|
const graphene_matrix_t *modelview)
|
|
{
|
|
RenderOp op;
|
|
|
|
if (builder->current_program &&
|
|
memcmp (&builder->program_state[builder->current_program->index].modelview, modelview,
|
|
sizeof (graphene_matrix_t)) == 0)
|
|
return;
|
|
|
|
if (builder->render_ops->len > 0)
|
|
{
|
|
RenderOp *last_op = &g_array_index (builder->render_ops, RenderOp, builder->render_ops->len - 1);
|
|
if (last_op->op == OP_CHANGE_MODELVIEW)
|
|
{
|
|
last_op->modelview = *modelview;
|
|
}
|
|
else
|
|
{
|
|
op.op = OP_CHANGE_MODELVIEW;
|
|
op.modelview = *modelview;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
op.op = OP_CHANGE_MODELVIEW;
|
|
op.modelview = *modelview;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
|
|
if (builder->current_program != NULL)
|
|
builder->program_state[builder->current_program->index].modelview = *modelview;
|
|
}
|
|
|
|
void
|
|
ops_push_modelview (RenderOpBuilder *builder,
|
|
const graphene_matrix_t *mv)
|
|
{
|
|
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->matrix = *mv;
|
|
extract_matrix_metadata (mv, &entry->metadata);
|
|
|
|
builder->current_modelview = &entry->matrix;
|
|
ops_set_modelview (builder, mv);
|
|
}
|
|
|
|
void
|
|
ops_pop_modelview (RenderOpBuilder *builder)
|
|
{
|
|
const graphene_matrix_t *m;
|
|
const MatrixStackEntry *head;
|
|
|
|
g_assert (builder->mv_stack);
|
|
g_assert (builder->mv_stack->len >= 1);
|
|
|
|
builder->mv_stack->len --;
|
|
head = &g_array_index (builder->mv_stack, MatrixStackEntry, builder->mv_stack->len - 1);
|
|
m = &head->matrix;
|
|
|
|
if (builder->mv_stack->len >= 1)
|
|
{
|
|
builder->current_modelview = m;
|
|
ops_set_modelview (builder, m);
|
|
}
|
|
else
|
|
{
|
|
builder->current_modelview = NULL;
|
|
}
|
|
}
|
|
|
|
graphene_matrix_t
|
|
ops_set_projection (RenderOpBuilder *builder,
|
|
const graphene_matrix_t *projection)
|
|
{
|
|
RenderOp op;
|
|
graphene_matrix_t prev_mv;
|
|
|
|
if (builder->render_ops->len > 0)
|
|
{
|
|
RenderOp *last_op = &g_array_index (builder->render_ops, RenderOp, builder->render_ops->len - 1);
|
|
if (last_op->op == OP_CHANGE_PROJECTION)
|
|
{
|
|
last_op->projection = *projection;
|
|
}
|
|
else
|
|
{
|
|
op.op = OP_CHANGE_PROJECTION;
|
|
op.projection = *projection;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
op.op = OP_CHANGE_PROJECTION;
|
|
op.projection = *projection;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
|
|
if (builder->current_program != NULL)
|
|
builder->program_state[builder->current_program->index].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)
|
|
{
|
|
RenderOp op;
|
|
graphene_rect_t prev_viewport;
|
|
|
|
op.op = OP_CHANGE_VIEWPORT;
|
|
op.viewport = *viewport;
|
|
g_array_append_val (builder->render_ops, op);
|
|
|
|
if (builder->current_program != NULL)
|
|
builder->program_state[builder->current_program->index].viewport = *viewport;
|
|
|
|
prev_viewport = builder->current_viewport;
|
|
builder->current_viewport = *viewport;
|
|
|
|
return prev_viewport;
|
|
}
|
|
|
|
void
|
|
ops_set_texture (RenderOpBuilder *builder,
|
|
int texture_id)
|
|
{
|
|
RenderOp op;
|
|
|
|
if (builder->current_texture == texture_id)
|
|
return;
|
|
|
|
op.op = OP_CHANGE_SOURCE_TEXTURE;
|
|
op.texture_id = texture_id;
|
|
g_array_append_val (builder->render_ops, op);
|
|
builder->current_texture = texture_id;
|
|
}
|
|
|
|
int
|
|
ops_set_render_target (RenderOpBuilder *builder,
|
|
int render_target_id)
|
|
{
|
|
RenderOp op;
|
|
int prev_render_target;
|
|
|
|
if (builder->current_render_target == render_target_id)
|
|
return render_target_id;
|
|
|
|
prev_render_target = builder->current_render_target;
|
|
op.op = OP_CHANGE_RENDER_TARGET;
|
|
op.render_target_id = render_target_id;
|
|
g_array_append_val (builder->render_ops, op);
|
|
builder->current_render_target = render_target_id;
|
|
|
|
return prev_render_target;
|
|
}
|
|
|
|
float
|
|
ops_set_opacity (RenderOpBuilder *builder,
|
|
float opacity)
|
|
{
|
|
RenderOp op;
|
|
float prev_opacity;
|
|
RenderOp *last_op;
|
|
|
|
if (builder->current_opacity == opacity)
|
|
return opacity;
|
|
|
|
if (builder->render_ops->len > 0)
|
|
{
|
|
last_op = &g_array_index (builder->render_ops, RenderOp, builder->render_ops->len - 1);
|
|
|
|
if (last_op->op == OP_CHANGE_OPACITY)
|
|
{
|
|
last_op->opacity = opacity;
|
|
}
|
|
else
|
|
{
|
|
op.op = OP_CHANGE_OPACITY;
|
|
op.opacity = opacity;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
op.op = OP_CHANGE_OPACITY;
|
|
op.opacity = opacity;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
|
|
prev_opacity = builder->current_opacity;
|
|
builder->current_opacity = opacity;
|
|
|
|
if (builder->current_program != NULL)
|
|
builder->program_state[builder->current_program->index].opacity = opacity;
|
|
|
|
return prev_opacity;
|
|
}
|
|
|
|
void
|
|
ops_set_color (RenderOpBuilder *builder,
|
|
const GdkRGBA *color)
|
|
{
|
|
RenderOp op;
|
|
|
|
if (gdk_rgba_equal (color, &builder->program_state[builder->current_program->index].color))
|
|
return;
|
|
|
|
builder->program_state[builder->current_program->index].color = *color;
|
|
|
|
op.op = OP_CHANGE_COLOR;
|
|
op.color = *color;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
|
|
void
|
|
ops_set_color_matrix (RenderOpBuilder *builder,
|
|
const graphene_matrix_t *matrix,
|
|
const graphene_vec4_t *offset)
|
|
{
|
|
RenderOp op;
|
|
|
|
if (memcmp (matrix,
|
|
&builder->program_state[builder->current_program->index].color_matrix.matrix,
|
|
sizeof (graphene_matrix_t)) == 0 &&
|
|
memcmp (offset,
|
|
&builder->program_state[builder->current_program->index].color_matrix.offset,
|
|
sizeof (graphene_vec4_t)) == 0)
|
|
return;
|
|
|
|
builder->program_state[builder->current_program->index].color_matrix.matrix = *matrix;
|
|
builder->program_state[builder->current_program->index].color_matrix.offset = *offset;
|
|
|
|
op.op = OP_CHANGE_COLOR_MATRIX;
|
|
op.color_matrix.matrix = *matrix;
|
|
op.color_matrix.offset = *offset;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
|
|
void
|
|
ops_set_border (RenderOpBuilder *builder,
|
|
const float *widths,
|
|
const GskRoundedRect *outline)
|
|
{
|
|
RenderOp op;
|
|
|
|
/* TODO: Assert that current_program == border program? */
|
|
|
|
if (memcmp (&builder->program_state[builder->current_program->index].border.widths,
|
|
widths, sizeof (float) * 4) == 0 &&
|
|
memcmp (&builder->program_state[builder->current_program->index].border.outline,
|
|
outline, sizeof (GskRoundedRect)) == 0)
|
|
return;
|
|
|
|
memcpy (&builder->program_state[builder->current_program->index].border.widths,
|
|
widths, sizeof (float) * 4);
|
|
|
|
builder->program_state[builder->current_program->index].border.outline = *outline;
|
|
|
|
op.op = OP_CHANGE_BORDER;
|
|
op.border.widths[0] = widths[0];
|
|
op.border.widths[1] = widths[1];
|
|
op.border.widths[2] = widths[2];
|
|
op.border.widths[3] = widths[3];
|
|
op.border.outline = *outline;
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
|
|
void
|
|
ops_set_border_color (RenderOpBuilder *builder,
|
|
const GdkRGBA *color)
|
|
{
|
|
RenderOp op;
|
|
op.op = OP_CHANGE_BORDER_COLOR;
|
|
rgba_to_float (color, op.border.color);
|
|
|
|
if (memcmp (&op.border.color, &builder->program_state[builder->current_program->index].border.color,
|
|
sizeof (float) * 4) == 0)
|
|
return;
|
|
|
|
rgba_to_float (color, builder->program_state[builder->current_program->index].border.color);
|
|
|
|
g_array_append_val (builder->render_ops, op);
|
|
}
|
|
|
|
void
|
|
ops_draw (RenderOpBuilder *builder,
|
|
const GskQuadVertex vertex_data[GL_N_VERTICES])
|
|
{
|
|
RenderOp *last_op;
|
|
|
|
last_op = &g_array_index (builder->render_ops, RenderOp, builder->render_ops->len - 1);
|
|
/* If the previous op was a DRAW as well, we didn't change anything between the two calls,
|
|
* so these are just 2 subsequent draw calls. Same VAO, same program etc.
|
|
* And the offsets into the vao are in order as well, so make it one draw call. */
|
|
if (last_op->op == OP_DRAW)
|
|
{
|
|
/* We allow ourselves a little trick here. We still have to add a CHANGE_VAO op for
|
|
* this draw call so we can add our vertex data there, but we want it to be placed before
|
|
* the last draw call, so we reorder those. */
|
|
RenderOp new_draw;
|
|
new_draw.op = OP_DRAW;
|
|
new_draw.draw.vao_offset = last_op->draw.vao_offset;
|
|
new_draw.draw.vao_size = last_op->draw.vao_size + GL_N_VERTICES;
|
|
|
|
last_op->op = OP_CHANGE_VAO;
|
|
memcpy (&last_op->vertex_data, vertex_data, sizeof(GskQuadVertex) * GL_N_VERTICES);
|
|
|
|
/* Now add the DRAW */
|
|
g_array_append_val (builder->render_ops, new_draw);
|
|
}
|
|
else
|
|
{
|
|
const gsize n_ops = builder->render_ops->len;
|
|
RenderOp *op;
|
|
gsize offset = builder->buffer_size / sizeof (GskQuadVertex);
|
|
|
|
/* We will add two render ops here. */
|
|
g_array_set_size (builder->render_ops, n_ops + 2);
|
|
|
|
op = &g_array_index (builder->render_ops, RenderOp, n_ops);
|
|
op->op = OP_CHANGE_VAO;
|
|
memcpy (&op->vertex_data, vertex_data, sizeof(GskQuadVertex) * GL_N_VERTICES);
|
|
|
|
op = &g_array_index (builder->render_ops, RenderOp, n_ops + 1);
|
|
op->op = OP_DRAW;
|
|
op->draw.vao_offset = offset;
|
|
op->draw.vao_size = GL_N_VERTICES;
|
|
}
|
|
|
|
/* We added new vertex data in both cases so increase the buffer size */
|
|
builder->buffer_size += sizeof (GskQuadVertex) * GL_N_VERTICES;
|
|
}
|
|
|
|
void
|
|
ops_offset (RenderOpBuilder *builder,
|
|
float x,
|
|
float y)
|
|
{
|
|
builder->dx += x;
|
|
builder->dy += y;
|
|
}
|
|
|
|
void
|
|
ops_add (RenderOpBuilder *builder,
|
|
const RenderOp *op)
|
|
{
|
|
g_array_append_val (builder->render_ops, *op);
|
|
}
|