gnome-shell/src/st/st-theme-node-transition.c
Florian Müllner 5bd977dd3c [transitions] Do not recreate FBOs on opacity changes
Creating an FBO may be expensive, so we should avoid the operation
if possible. When transitioning between theme nodes, the widget's
opacity is used to paint to the offscreen textures which are blended
together - this means that the textures have to be recreated each time
the widget's opacity changes. It is much more effective to paint the
textures at full opacity and respect the widget's paint opacity when
blending the textures together.

https://bugzilla.gnome.org/show_bug.cgi?id=627085
2010-08-23 18:41:34 +02:00

439 lines
14 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* Theme node transitions for StWidget.
*
* Copyright (C) 2010 Florian Müllner <fmuellner@gnome.org>
*
* The St is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* The St is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with the St; see the file COPYING.LIB.
* If not, write to the Free Software Foundation, Inc., 59 Temple Place -
* Suite 330, Boston, MA 02111-1307, USA.
*/
#include "st-theme-node-transition.h"
enum {
COMPLETED,
NEW_FRAME,
LAST_SIGNAL
};
#define ST_THEME_NODE_TRANSITION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_THEME_NODE_TRANSITION, StThemeNodeTransitionPrivate))
struct _StThemeNodeTransitionPrivate {
StThemeNode *old_theme_node;
StThemeNode *new_theme_node;
CoglHandle old_texture;
CoglHandle new_texture;
CoglHandle old_offscreen;
CoglHandle new_offscreen;
CoglHandle material;
ClutterAlpha *alpha;
ClutterTimeline *timeline;
guint timeline_completed_id;
guint timeline_new_frame_id;
ClutterActorBox last_allocation;
ClutterActorBox offscreen_box;
gboolean needs_setup;
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (StThemeNodeTransition, st_theme_node_transition, G_TYPE_OBJECT);
static void
on_timeline_completed (ClutterTimeline *timeline,
StThemeNodeTransition *transition)
{
g_signal_emit (transition, signals[COMPLETED], 0);
}
static void
on_timeline_new_frame (ClutterTimeline *timeline,
gint frame_num,
StThemeNodeTransition *transition)
{
g_signal_emit (transition, signals[NEW_FRAME], 0);
}
StThemeNodeTransition *
st_theme_node_transition_new (StThemeNode *from_node,
StThemeNode *to_node,
guint duration)
{
StThemeNodeTransition *transition;
g_return_val_if_fail (ST_IS_THEME_NODE (from_node), NULL);
g_return_val_if_fail (ST_IS_THEME_NODE (to_node), NULL);
duration = st_theme_node_get_transition_duration (to_node);
transition = g_object_new (ST_TYPE_THEME_NODE_TRANSITION,
NULL);
transition->priv->old_theme_node = g_object_ref (from_node);
transition->priv->new_theme_node = g_object_ref (to_node);
transition->priv->alpha = clutter_alpha_new ();
transition->priv->timeline = clutter_timeline_new (duration);
transition->priv->timeline_completed_id =
g_signal_connect (transition->priv->timeline, "completed",
G_CALLBACK (on_timeline_completed), transition);
transition->priv->timeline_new_frame_id =
g_signal_connect (transition->priv->timeline, "new-frame",
G_CALLBACK (on_timeline_new_frame), transition);
clutter_alpha_set_mode (transition->priv->alpha, CLUTTER_EASE_IN_OUT_QUAD);
clutter_alpha_set_timeline (transition->priv->alpha,
transition->priv->timeline);
clutter_timeline_start (transition->priv->timeline);
return transition;
}
void
st_theme_node_transition_update (StThemeNodeTransition *transition,
StThemeNode *new_node)
{
StThemeNodeTransitionPrivate *priv = transition->priv;
StThemeNode *old_node;
ClutterTimelineDirection direction;
g_return_if_fail (ST_IS_THEME_NODE_TRANSITION (transition));
g_return_if_fail (ST_IS_THEME_NODE (new_node));
direction = clutter_timeline_get_direction (priv->timeline);
old_node = (direction == CLUTTER_TIMELINE_FORWARD) ? priv->old_theme_node
: priv->new_theme_node;
/* If the update is the reversal of the current transition,
* we reverse the timeline.
* Otherwise, we should initiate a new transition from the
* current state to the new one; this is hard to do if the
* transition is in an intermediate state, so we just cancel
* the ongoing transition in that case.
* Note that reversing a timeline before any time elapsed
* results in the timeline's time position being set to the
* full duration - this is not what we want, so we cancel the
* transition as well in that case.
*/
if (st_theme_node_equal (new_node, old_node))
{
if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
{
if (direction == CLUTTER_TIMELINE_FORWARD)
clutter_timeline_set_direction (priv->timeline,
CLUTTER_TIMELINE_BACKWARD);
else
clutter_timeline_set_direction (priv->timeline,
CLUTTER_TIMELINE_FORWARD);
}
else
{
clutter_timeline_stop (priv->timeline);
g_signal_emit (transition, signals[COMPLETED], 0);
}
}
else
{
if (clutter_timeline_get_elapsed_time (priv->timeline) > 0)
{
clutter_timeline_stop (priv->timeline);
g_signal_emit (transition, signals[COMPLETED], 0);
}
else
{
guint new_duration = st_theme_node_get_transition_duration (new_node);
clutter_timeline_set_duration (priv->timeline, new_duration);
g_object_unref (priv->new_theme_node);
priv->new_theme_node = g_object_ref (new_node);
priv->needs_setup = TRUE;
}
}
}
static void
calculate_offscreen_box (StThemeNodeTransition *transition,
const ClutterActorBox *allocation)
{
StThemeNodeTransitionPrivate *priv = transition->priv;
ClutterActorBox old_node_box, new_node_box;
st_theme_node_get_paint_box (priv->old_theme_node,
allocation,
&old_node_box);
st_theme_node_get_paint_box (priv->new_theme_node,
allocation,
&new_node_box);
priv->offscreen_box.x1 = MIN (old_node_box.x1, new_node_box.x1)
- allocation->x1;
priv->offscreen_box.y1 = MIN (old_node_box.y1, new_node_box.y1)
- allocation->y1;
priv->offscreen_box.x2 = MAX (old_node_box.x2, new_node_box.x2)
- allocation->x1;
priv->offscreen_box.y2 = MAX (old_node_box.y2, new_node_box.y2)
- allocation->y1;
}
static gboolean
setup_framebuffers (StThemeNodeTransition *transition,
const ClutterActorBox *allocation)
{
StThemeNodeTransitionPrivate *priv = transition->priv;
CoglColor clear_color = { 0, 0, 0, 0 };
guint width, height;
width = priv->offscreen_box.x2 - priv->offscreen_box.x1;
height = priv->offscreen_box.y2 - priv->offscreen_box.y1;
g_return_val_if_fail (width > 0, FALSE);
g_return_val_if_fail (height > 0, FALSE);
if (priv->old_texture)
cogl_handle_unref (priv->old_texture);
priv->old_texture = cogl_texture_new_with_size (width, height,
COGL_TEXTURE_NO_SLICING,
COGL_PIXEL_FORMAT_ANY);
if (priv->new_texture)
cogl_handle_unref (priv->new_texture);
priv->new_texture = cogl_texture_new_with_size (width, height,
COGL_TEXTURE_NO_SLICING,
COGL_PIXEL_FORMAT_ANY);
g_return_val_if_fail (priv->old_texture != COGL_INVALID_HANDLE, FALSE);
g_return_val_if_fail (priv->new_texture != COGL_INVALID_HANDLE, FALSE);
if (priv->old_offscreen)
cogl_handle_unref (priv->old_offscreen);
priv->old_offscreen = cogl_offscreen_new_to_texture (priv->old_texture);
if (priv->new_offscreen)
cogl_handle_unref (priv->new_offscreen);
priv->new_offscreen = cogl_offscreen_new_to_texture (priv->new_texture);
g_return_val_if_fail (priv->old_offscreen != COGL_INVALID_HANDLE, FALSE);
g_return_val_if_fail (priv->new_offscreen != COGL_INVALID_HANDLE, FALSE);
if (priv->material)
cogl_handle_unref (priv->material);
priv->material = cogl_material_new ();
cogl_material_set_layer_combine (priv->material, 0,
"RGBA = REPLACE (TEXTURE)",
NULL);
cogl_material_set_layer_combine (priv->material, 1,
"RGBA = INTERPOLATE (PREVIOUS, "
"TEXTURE, "
"CONSTANT[A])",
NULL);
cogl_material_set_layer_combine (priv->material, 2,
"RGBA = MODULATE (PREVIOUS, PRIMARY)",
NULL);
cogl_material_set_layer (priv->material, 0, priv->new_texture);
cogl_material_set_layer (priv->material, 1, priv->old_texture);
cogl_push_framebuffer (priv->old_offscreen);
cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
cogl_ortho (priv->offscreen_box.x1, priv->offscreen_box.x2,
priv->offscreen_box.y2, priv->offscreen_box.y1,
0.0, 1.0);
st_theme_node_paint (priv->old_theme_node, allocation, 255);
cogl_pop_framebuffer ();
cogl_push_framebuffer (priv->new_offscreen);
cogl_clear (&clear_color, COGL_BUFFER_BIT_COLOR);
cogl_ortho (priv->offscreen_box.x1, priv->offscreen_box.x2,
priv->offscreen_box.y2, priv->offscreen_box.y1,
0.0, 1.0);
st_theme_node_paint (priv->new_theme_node, allocation, 255);
cogl_pop_framebuffer ();
return TRUE;
}
void
st_theme_node_transition_paint (StThemeNodeTransition *transition,
ClutterActorBox *allocation,
guint8 paint_opacity)
{
StThemeNodeTransitionPrivate *priv = transition->priv;
CoglColor constant;
float tex_coords[] = {
0.0, 0.0, 1.0, 1.0,
0.0, 0.0, 1.0, 1.0,
};
g_return_if_fail (ST_IS_THEME_NODE (priv->old_theme_node));
g_return_if_fail (ST_IS_THEME_NODE (priv->new_theme_node));
if (!clutter_actor_box_equal (allocation, &priv->last_allocation))
priv->needs_setup = TRUE;
if (priv->needs_setup)
{
priv->last_allocation = *allocation;
calculate_offscreen_box (transition, allocation);
priv->needs_setup = !setup_framebuffers (transition, allocation);
}
cogl_color_set_from_4f (&constant, 0., 0., 0.,
clutter_alpha_get_alpha (priv->alpha));
cogl_material_set_layer_combine_constant (priv->material, 1, &constant);
cogl_material_set_color4ub (priv->material,
paint_opacity, paint_opacity,
paint_opacity, paint_opacity);
cogl_set_source (priv->material);
cogl_rectangle_with_multitexture_coords (priv->offscreen_box.x1,
priv->offscreen_box.y1,
priv->offscreen_box.x2,
priv->offscreen_box.y2,
tex_coords, 8);
}
static void
st_theme_node_transition_dispose (GObject *object)
{
StThemeNodeTransitionPrivate *priv = ST_THEME_NODE_TRANSITION (object)->priv;
if (priv->old_theme_node)
{
g_object_unref (priv->old_theme_node);
priv->old_theme_node = NULL;
}
if (priv->new_theme_node)
{
g_object_unref (priv->new_theme_node);
priv->new_theme_node = NULL;
}
if (priv->old_texture)
{
cogl_handle_unref (priv->old_texture);
priv->old_texture = NULL;
}
if (priv->new_texture)
{
cogl_handle_unref (priv->new_texture);
priv->new_texture = NULL;
}
if (priv->old_offscreen)
{
cogl_handle_unref (priv->old_offscreen);
priv->old_offscreen = NULL;
}
if (priv->new_offscreen)
{
cogl_handle_unref (priv->new_offscreen);
priv->new_offscreen = NULL;
}
if (priv->material)
{
cogl_handle_unref (priv->material);
priv->material = NULL;
}
if (priv->timeline)
{
if (priv->timeline_completed_id != 0)
g_signal_handler_disconnect (priv->timeline,
priv->timeline_completed_id);
if (priv->timeline_new_frame_id != 0)
g_signal_handler_disconnect (priv->timeline,
priv->timeline_new_frame_id);
g_object_unref (priv->timeline);
priv->timeline = NULL;
}
priv->timeline_completed_id = 0;
priv->timeline_new_frame_id = 0;
if (priv->alpha)
{
g_object_unref (priv->alpha);
priv->alpha = NULL;
}
G_OBJECT_CLASS (st_theme_node_transition_parent_class)->dispose (object);
}
static void
st_theme_node_transition_init (StThemeNodeTransition *transition)
{
transition->priv = ST_THEME_NODE_TRANSITION_GET_PRIVATE (transition);
transition->priv->old_theme_node = NULL;
transition->priv->new_theme_node = NULL;
transition->priv->old_texture = NULL;
transition->priv->new_texture = NULL;
transition->priv->old_offscreen = NULL;
transition->priv->new_offscreen = NULL;
transition->priv->needs_setup = TRUE;
transition->priv->alpha = NULL;
}
static void
st_theme_node_transition_class_init (StThemeNodeTransitionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (StThemeNodeTransitionPrivate));
object_class->dispose = st_theme_node_transition_dispose;
signals[COMPLETED] =
g_signal_new ("completed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StThemeNodeTransitionClass, completed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[NEW_FRAME] =
g_signal_new ("new-frame",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StThemeNodeTransitionClass, new_frame),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}