GtkSnapshot: Add gtk_snapshot_push_glshader()

This commit is contained in:
Alexander Larsson
2020-09-18 17:56:02 +02:00
parent 7ea755e206
commit 950cc41e15
3 changed files with 207 additions and 1 deletions

View File

@@ -4299,6 +4299,7 @@ gtk_snapshot_push_blend
gtk_snapshot_push_blur
gtk_snapshot_push_shadow
gtk_snapshot_push_debug
gtk_snapshot_push_gl_shader
gtk_snapshot_pop
gtk_snapshot_save
gtk_snapshot_restore

View File

@@ -91,6 +91,18 @@ struct _GtkSnapshotState {
struct {
graphene_rect_t bounds;
} clip;
struct {
GskGLShader *shader;
GBytes *args;
graphene_rect_t bounds;
GskRenderNode **nodes;
GskRenderNode *internal_nodes[4];
} glshader;
struct {
graphene_rect_t bounds;
int node_idx;
int n_children;
} glshader_texture;
struct {
GskRoundedRect bounds;
} rounded_clip;
@@ -230,6 +242,18 @@ gtk_snapshot_get_previous_state (const GtkSnapshot *snapshot)
return gtk_snapshot_states_get (&snapshot->state_stack, size - 2);
}
/* n == 0 => current, n == 1, previous, etc */
static GtkSnapshotState *
gtk_snapshot_get_nth_previous_state (const GtkSnapshot *snapshot,
int n)
{
gsize size = gtk_snapshot_states_get_size (&snapshot->state_stack);
g_assert (size > n);
return gtk_snapshot_states_get (&snapshot->state_stack, size - (1 + n));
}
static void
gtk_snapshot_state_clear (GtkSnapshotState *state)
{
@@ -825,6 +849,149 @@ gtk_snapshot_push_clip (GtkSnapshot *snapshot,
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &state->data.clip.bounds);
}
static GskRenderNode *
gtk_snapshot_collect_gl_shader (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **collected_nodes,
guint n_collected_nodes)
{
GskRenderNode *shader_node = NULL;
GskRenderNode **nodes;
int n_children;
n_children = gsk_gl_shader_get_n_textures (state->data.glshader.shader);
shader_node = NULL;
if (n_collected_nodes != 0)
g_warning ("Unexpected children when poping gl shader.");
if (state->data.glshader.nodes)
nodes = state->data.glshader.nodes;
else
nodes = &state->data.glshader.internal_nodes[0];
if (state->data.glshader.bounds.size.width != 0 &&
state->data.glshader.bounds.size.height != 0)
shader_node = gsk_gl_shader_node_new (state->data.glshader.shader,
&state->data.glshader.bounds,
state->data.glshader.args,
nodes, n_children);
g_object_unref (state->data.glshader.shader);
g_bytes_unref (state->data.glshader.args);
for (guint i = 0; i < n_children; i++)
gsk_render_node_unref (nodes[i]);
g_free (state->data.glshader.nodes);
return shader_node;
}
static GskRenderNode *
gtk_snapshot_collect_gl_shader_texture (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *child_node;
GdkRGBA transparent = { 0, 0, 0, 0 };
int n_children, node_idx;
GtkSnapshotState *glshader_state;
GskRenderNode **out_nodes;
child_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (child_node == NULL)
child_node = gsk_color_node_new (&transparent, &state->data.glshader_texture.bounds);
n_children = state->data.glshader_texture.n_children;
node_idx = state->data.glshader_texture.node_idx;
glshader_state = gtk_snapshot_get_nth_previous_state (snapshot, n_children - node_idx);
g_assert (glshader_state->collect_func == gtk_snapshot_collect_gl_shader);
if (glshader_state->data.glshader.nodes)
out_nodes = glshader_state->data.glshader.nodes;
else
out_nodes = &glshader_state->data.glshader.internal_nodes[0];
out_nodes[node_idx] = child_node;
return NULL;
}
/**
* gtk_snapshot_push_gl_shader:
* @snapshot: a #GtkSnapshot
* @shader: The code to run
* @bounds: the rectangle to render into
* @take_args: (transfer full): Data block with arguments for the shader.
*
* Push a #GskGLShaderNode with a specific #GskGLShader and a set of uniform values
* to use while rendering. Additionally this takes a list of @n_children other nodes
* which will be passed to the #GskGLShaderNode.
*
* The @take_args argument is a block of data to use for uniform
* arguments, as per types and offsets defined by the @shader. Normally this is
* generated by gsk_gl_shader_format_args() or #GskGLShaderArgBuilder.
* The snapshotter takes ownership of @take_args, so the caller should not free it
* after this.
*
* If the renderer doesn't support GL shaders, or if there is any problem when
* compiling the shader, then the node will draw pink. You should use
* gsk_gl_shader_compile() to ensure the @shader will work for the renderer
* before using it.
*
* If the shader requires textures (see gsk_gl_shader_get_n_textures()), then it is
* expected that you call gtk_snapshot_gl_shader_pop_texture() the number of times that are
* required. Each of these calls will generate a node that is added as a child to the gl shader
* node, which in turn will render these offscreen and pass as a texture to the shader.
*
* Once all textures (if any) are pop:ed, you must call the regular gtk_snapshot_pop().
*
* If you want to use pre-existing textures as input to the shader rather than
* rendering new ones, use gtk_snapshot_append_texture() to push a texture node. These
* will be used directly rather than being re-rendered.
*
* For details on how to write shaders, see #GskGLShader.
*/
void
gtk_snapshot_push_gl_shader (GtkSnapshot *snapshot,
GskGLShader *shader,
const graphene_rect_t *bounds,
GBytes *take_args)
{
GtkSnapshotState *state;
float scale_x, scale_y, dx, dy;
graphene_rect_t transformed_bounds;
int n_children = gsk_gl_shader_get_n_textures (shader);
gtk_snapshot_ensure_affine (snapshot, &scale_x, &scale_y, &dx, &dy);
state = gtk_snapshot_push_state (snapshot,
gtk_snapshot_get_current_state (snapshot)->transform,
gtk_snapshot_collect_gl_shader);
gtk_graphene_rect_scale_affine (bounds, scale_x, scale_y, dx, dy, &transformed_bounds);
state->data.glshader.bounds = transformed_bounds;
state->data.glshader.shader = g_object_ref (shader);
state->data.glshader.args = take_args; /* Takes ownership */
if (n_children <= G_N_ELEMENTS (state->data.glshader.internal_nodes))
state->data.glshader.nodes = NULL;
else
state->data.glshader.nodes = g_new (GskRenderNode *, n_children);
for (int i = 0; i < n_children; i++)
{
state = gtk_snapshot_push_state (snapshot,
gtk_snapshot_get_current_state (snapshot)->transform,
gtk_snapshot_collect_gl_shader_texture);
state->data.glshader_texture.bounds = transformed_bounds;
state->data.glshader_texture.node_idx = n_children - 1 - i;/* We pop in reverse order */
state->data.glshader_texture.n_children = n_children;
}
}
static GskRenderNode *
gtk_snapshot_collect_rounded_clip (GtkSnapshot *snapshot,
GtkSnapshotState *state,
@@ -1325,14 +1492,46 @@ gtk_snapshot_to_paintable (GtkSnapshot *snapshot,
void
gtk_snapshot_pop (GtkSnapshot *snapshot)
{
GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
if (state->collect_func == gtk_snapshot_collect_gl_shader_texture)
g_warning ("Not enough calls to gtk_snapshot_gl_shader_pop_texture().");
node = gtk_snapshot_pop_internal (snapshot);
if (node)
gtk_snapshot_append_node_internal (snapshot, node);
}
/**
* gtk_snapshot_gl_shader_pop_texture:
* @snapshot: a #GtkSnapshot
*
* Removes the top element from the stack of render nodes and
* adds it to the nearest GskGLShaderNode below it. This must be called the
* same number of times as the number of textures is needed for the
* shader in gtk_snapshot_push_gl_shader().
*/
void
gtk_snapshot_gl_shader_pop_texture (GtkSnapshot *snapshot)
{
GtkSnapshotState *state = gtk_snapshot_get_current_state (snapshot);
GskRenderNode *node;
if (state->collect_func != gtk_snapshot_collect_gl_shader_texture)
{
g_warning ("Too many calls to gtk_snapshot_gl_shader_pop_texture().");
return;
}
g_assert (state->collect_func == gtk_snapshot_collect_gl_shader_texture);
node = gtk_snapshot_pop_internal (snapshot);
g_assert (node == NULL);
}
/**
* gtk_snapshot_save:
* @snapshot: a #GtkSnapshot

View File

@@ -99,8 +99,14 @@ GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_cross_fade (GtkSnapshot *snapshot,
double progress);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_gl_shader (GtkSnapshot *snapshot,
GskGLShader *shader,
const graphene_rect_t *bounds,
GBytes *take_args);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_gl_shader_pop_texture (GtkSnapshot *snapshot);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_pop (GtkSnapshot *snapshot);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_save (GtkSnapshot *snapshot);
GDK_AVAILABLE_IN_ALL