[gimp/wip/animation: 98/145] plug-ins: move all the caching to a separate renderer.



commit 81337cfbf278a84034303db5e10bb94a7cdc2ae5
Author: Jehan <jehan girinstud io>
Date:   Thu May 18 23:21:06 2017 +0200

    plug-ins: move all the caching to a separate renderer.
    
    This is a first step towards a threaded rendering which won't block the
    GUI.

 plug-ins/animation-play/Makefile.am                |    2 +
 plug-ins/animation-play/core/animation-animatic.c  |  242 +++-----
 plug-ins/animation-play/core/animation-animatic.h  |    2 +-
 .../animation-play/core/animation-celanimation.c   |  689 +++++++-------------
 plug-ins/animation-play/core/animation-playback.c  |  106 ++--
 plug-ins/animation-play/core/animation-playback.h  |    2 +
 plug-ins/animation-play/core/animation-renderer.c  |  355 ++++++++++
 plug-ins/animation-play/core/animation-renderer.h  |   60 ++
 plug-ins/animation-play/core/animation.c           |   49 +-
 plug-ins/animation-play/core/animation.h           |   25 +-
 plug-ins/animation-play/widgets/animation-dialog.c |   18 +-
 plug-ins/animation-play/widgets/animation-xsheet.c |   12 +-
 12 files changed, 841 insertions(+), 721 deletions(-)
---
diff --git a/plug-ins/animation-play/Makefile.am b/plug-ins/animation-play/Makefile.am
index b4f88d4..b47a8f1 100644
--- a/plug-ins/animation-play/Makefile.am
+++ b/plug-ins/animation-play/Makefile.am
@@ -53,6 +53,8 @@ animation_play_SOURCES = \
        core/animation-celanimation.c   \
        core/animation-playback.h               \
        core/animation-playback.c               \
+       core/animation-renderer.h               \
+       core/animation-renderer.c               \
        widgets/animation-dialog.h              \
        widgets/animation-dialog.c              \
        widgets/animation-keyframe-view.h       \
diff --git a/plug-ins/animation-play/core/animation-animatic.c 
b/plug-ins/animation-play/core/animation-animatic.c
index 92fe0a0..fd2e789 100644
--- a/plug-ins/animation-play/core/animation-animatic.c
+++ b/plug-ins/animation-play/core/animation-animatic.c
@@ -24,7 +24,9 @@
 #include <libgimp/stdplugins-intl.h>
 
 #include "animation-utils.h"
+
 #include "animation-animatic.h"
+#include "animation-renderer.h"
 
 typedef enum
 {
@@ -61,8 +63,6 @@ typedef struct _AnimationAnimaticPrivate AnimationAnimaticPrivate;
 
 struct _AnimationAnimaticPrivate
 {
-  /* Panels are cached as GEGL buffers. */
-  GeglBuffer **cache;
   /* The number of panels. */
   gint         n_panels;
   /* Layers associated to each panel. For serialization. */
@@ -86,14 +86,11 @@ static void         animation_animatic_finalize       (GObject           *object
 
 static gint         animation_animatic_get_duration   (Animation         *animation);
 
-static GeglBuffer * animation_animatic_get_frame      (Animation         *animation,
-                                                       gint               pos);
-
-static gboolean     animation_animatic_same           (Animation         *animation,
-                                                       gint               pos1,
-                                                       gint               pos2);
-
-static void         animation_animatic_purge_cache    (Animation         *animation);
+static gchar      * animation_animatic_get_frame_hash (Animation         *animation,
+                                                       gint               position);
+static GeglBuffer * animation_animatic_create_frame   (Animation         *animation,
+                                                       GObject           *renderer,
+                                                       gint               position);
 
 static void         animation_animatic_reset_defaults (Animation         *animation);
 static gchar      * animation_animatic_serialize      (Animation         *animation,
@@ -121,12 +118,6 @@ static void      animation_animatic_text          (GMarkupParseContext  *context
                                                    gpointer              user_data,
                                                    GError              **error);
 
-/* Utils */
-
-static void      animation_animatic_cache_panel   (AnimationAnimatic    *animation,
-                                                   gint                  panel,
-                                                   gboolean              recursion);
-
 /* Tag handling (from layer names) */
 
 static gint      parse_ms_tag                     (Animation            *animation,
@@ -174,11 +165,8 @@ animation_animatic_class_init (AnimationAnimaticClass *klass)
   object_class->finalize     = animation_animatic_finalize;
 
   anim_class->get_duration   = animation_animatic_get_duration;
-  anim_class->get_frame      = animation_animatic_get_frame;
-
-  anim_class->same           = animation_animatic_same;
-
-  anim_class->purge_cache    = animation_animatic_purge_cache;
+  anim_class->get_frame_hash = animation_animatic_get_frame_hash;
+  anim_class->create_frame   = animation_animatic_create_frame;
 
   anim_class->reset_defaults = animation_animatic_reset_defaults;
   anim_class->serialize      = animation_animatic_serialize;
@@ -212,15 +200,6 @@ animation_animatic_finalize (GObject *object)
         }
       g_free (priv->comments);
     }
-  if (priv->cache)
-    {
-      for (i = 0; i < priv->n_panels; i++)
-        {
-          if (priv->cache[i])
-            g_object_unref (priv->cache[i]);
-        }
-      g_free (priv->cache);
-    }
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
@@ -303,8 +282,32 @@ animation_animatic_set_combine (AnimationAnimatic *animatic,
 
   if (priv->combine[panel_num] != combine)
     {
+      gint i;
+
       priv->combine[panel_num] = combine;
-      animation_animatic_cache_panel (animatic, panel_num, TRUE);
+
+      if (animation_animatic_get_panel_duration (animatic, panel_num) > 0)
+        g_signal_emit_by_name (animatic, "frames-changed",
+                               animation_animatic_get_position (animatic,
+                                                                panel_num),
+                               animation_animatic_get_panel_duration (animatic,
+                                                                      panel_num));
+
+      /* If next panel is in "combine" mode, it must also be re-cached.
+       * And so on, recursively. */
+      for (i = panel_num + 1; i < priv->n_panels; i++)
+        {
+          if (priv->combine[i])
+            {
+              g_signal_emit_by_name (animatic, "frames-changed",
+                                     animation_animatic_get_position (animatic, i),
+                                     animation_animatic_get_panel_duration (animatic, i));
+            }
+          else
+            {
+              break;
+            }
+        }
     }
 }
 
@@ -382,64 +385,78 @@ animation_animatic_get_duration (Animation *animation)
   return count;
 }
 
-static GeglBuffer *
-animation_animatic_get_frame (Animation *animation,
-                              gint       pos)
+static gchar *
+animation_animatic_get_frame_hash (Animation *animation,
+                                   gint       position)
 {
-  AnimationAnimaticPrivate *priv;
-  GeglBuffer               *frame = NULL;
+  AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
+  gchar                    *hash;
   gint                      panel;
 
-  priv = GET_PRIVATE (animation);
   panel = animation_animatic_get_panel (ANIMATION_ANIMATIC (animation),
-                                        pos);
-  if (priv->cache[panel])
-    frame = g_object_ref (priv->cache[panel]);
-
-  return frame;
+                                        position);
+  hash = g_strdup_printf ("%d;", priv->tattoos[panel]);
+  while (panel > 0 && priv->combine[panel--])
+    {
+      gchar *tmp = hash;
+      hash = g_strdup_printf ("%s%d;", hash, priv->tattoos[panel]);
+      g_free (tmp);
+    }
+  return hash;
 }
 
-static gboolean
-animation_animatic_same (Animation *animation,
-                         gint       pos1,
-                         gint       pos2)
+static GeglBuffer *
+animation_animatic_create_frame (Animation *animation,
+                                 GObject   *renderer,
+                                 gint       position)
 {
   AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
-  gint                      count = 0;
-  gboolean                  identical = FALSE;
-  gint                      i ;
+  GeglBuffer               *buffer;
+  GeglBuffer               *buffer2;
+  GeglBuffer               *backdrop = NULL;
+  gint                      layer_offx;
+  gint                      layer_offy;
+  gint                      preview_width;
+  gint                      preview_height;
+  gint32                    image_id;
+  gint32                    layer;
+  gdouble                   proxy_ratio;
+  gint                      panel;
 
-  for (i = 0; i < priv->n_panels; i++)
+  panel = animation_animatic_get_panel (ANIMATION_ANIMATIC (animation),
+                                        position);
+  image_id = animation_get_image_id (animation);
+  layer = gimp_image_get_layer_by_tattoo (image_id,
+                                          priv->tattoos[panel]);
+  if (! layer)
     {
-      count += priv->durations[i];
-      if (count > pos1 && count > pos2)
-        {
-          identical = TRUE;
-          break;
-        }
-      else if (count > pos1 || count > pos2)
-        {
-          identical = FALSE;
-          break;
-        }
+      g_printerr ("Warning: buffer creation of panel %d failed; "
+                  "the associated layer must have been deleted.\n",
+                  panel);
+      return NULL;
     }
 
-  return identical;
-}
-
-static void
-animation_animatic_purge_cache (Animation *animation)
-{
-  AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
-  gint                      i;
+  proxy_ratio = animation_get_proxy (animation);
+  buffer2 = gimp_drawable_get_buffer (layer);
+  animation_get_size (animation, &preview_width, &preview_height);
+  gimp_drawable_offsets (layer,
+                         &layer_offx, &layer_offy);
 
-  for (i = 0; i < priv->n_panels; i++)
+  if (panel > 0 && priv->combine[panel])
     {
-      animation_animatic_cache_panel (ANIMATION_ANIMATIC (animation), i, FALSE);
-      g_signal_emit_by_name (animation, "loading",
-                             (gdouble) i / ((gdouble) priv->n_panels - 0.999));
+      backdrop = animation_renderer_get_buffer (ANIMATION_RENDERER (renderer),
+                                                panel - 1);
     }
-  g_signal_emit_by_name (animation, "loaded");
+
+  buffer = normal_blend (preview_width, preview_height,
+                         backdrop, 1.0, 0, 0,
+                         buffer2, proxy_ratio,
+                         layer_offx, layer_offy);
+  g_object_unref (buffer2);
+  if (backdrop)
+    g_object_unref (backdrop);
+
+  return buffer;
 }
 
 static void
@@ -457,11 +474,8 @@ animation_animatic_reset_defaults (Animation *animation)
   for (i = 0; i < priv->n_panels; i++)
     {
       g_free (priv->comments[i]);
-      if (priv->cache[i])
-        g_object_unref (priv->cache[i]);
     }
   g_free (priv->comments);
-  g_free (priv->cache);
 
   image_id = animation_get_image_id (animation);
   layers   = gimp_image_get_layers (image_id, &priv->n_panels);
@@ -470,10 +484,8 @@ animation_animatic_reset_defaults (Animation *animation)
   priv->durations = g_try_malloc0_n (priv->n_panels, sizeof (gint));
   priv->combine   = g_try_malloc0_n (priv->n_panels, sizeof (gboolean));
   priv->comments  = g_try_malloc0_n (priv->n_panels, sizeof (gchar*));
-  priv->cache     = g_try_malloc0_n (priv->n_panels, sizeof (GeglBuffer*));
   if (! priv->tattoos || ! priv->durations ||
-      ! priv->combine || ! priv->comments  ||
-      ! priv->cache)
+      ! priv->combine || ! priv->comments)
     {
       gimp_message (_("Memory could not be allocated to the animatic."));
       gimp_quit ();
@@ -841,76 +853,6 @@ animation_animatic_text (GMarkupParseContext  *context,
     }
 }
 
-/**** Utils ****/
-
-static void
-animation_animatic_cache_panel (AnimationAnimatic *animatic,
-                                gint               panel,
-                                gboolean           recursion)
-{
-  AnimationAnimaticPrivate *priv      = GET_PRIVATE (animatic);
-  Animation                *animation = ANIMATION (animatic);
-  GeglBuffer               *buffer;
-  GeglBuffer               *backdrop_buffer = NULL;
-  gint                      layer_offx;
-  gint                      layer_offy;
-  gint                      preview_width;
-  gint                      preview_height;
-  gint32                    image_id;
-  gint32                    layer;
-  gdouble                   proxy_ratio;
-
-  image_id = animation_get_image_id (animation);
-  layer = gimp_image_get_layer_by_tattoo (image_id,
-                                          priv->tattoos[panel]);
-  if (! layer)
-    {
-      g_printerr ("Warning: caching of panel %d failed; "
-                  "the associated layer must have been deleted.\n",
-                  panel);
-      return;
-    }
-
-  /* Destroy existing cache. */
-  if (priv->cache[panel])
-    {
-      g_object_unref (priv->cache[panel]);
-    }
-
-  proxy_ratio = animation_get_proxy (animation);
-  buffer = gimp_drawable_get_buffer (layer);
-  animation_get_size (animation, &preview_width, &preview_height);
-  gimp_drawable_offsets (layer,
-                         &layer_offx, &layer_offy);
-
-  if (panel > 0 && priv->combine[panel])
-    {
-      backdrop_buffer = priv->cache[panel - 1];
-    }
-
-  priv->cache[panel] = normal_blend (preview_width, preview_height,
-                                     backdrop_buffer, 1.0, 0, 0,
-                                     buffer, proxy_ratio,
-                                     layer_offx, layer_offy);
-  g_object_unref (buffer);
-
-  if (animation_animatic_get_panel_duration (animatic, panel) > 0)
-    g_signal_emit_by_name (animation, "cache-invalidated",
-                           animation_animatic_get_position (animatic,
-                                                            panel),
-                           animation_animatic_get_panel_duration (animatic,
-                                                                  panel));
-
-  /* If next panel is in "combine" mode, it must also be re-cached.
-   * And so on, recursively. */
-  if (recursion                  &&
-      panel < priv->n_panels - 1 &&
-      priv->combine[panel + 1])
-    {
-      animation_animatic_cache_panel (animatic, panel + 1, TRUE);
-    }
-}
-
 /**** TAG UTILS ****/
 
 static gint
diff --git a/plug-ins/animation-play/core/animation-animatic.h 
b/plug-ins/animation-play/core/animation-animatic.h
index fccd85c..7860c0a 100644
--- a/plug-ins/animation-play/core/animation-animatic.h
+++ b/plug-ins/animation-play/core/animation-animatic.h
@@ -64,7 +64,7 @@ const gboolean   animation_animatic_get_combine        (AnimationAnimatic *anima
                                                         gint               panel);
 
 gint             animation_animatic_get_panel          (AnimationAnimatic *animation,
-                                                        gint               pos);
+                                                        gint               position);
 gint             animation_animatic_get_position       (AnimationAnimatic *animation,
                                                         gint               panel);
 
diff --git a/plug-ins/animation-play/core/animation-celanimation.c 
b/plug-ins/animation-play/core/animation-celanimation.c
index 5ec24e6..cbe2bb5 100644
--- a/plug-ins/animation-play/core/animation-celanimation.c
+++ b/plug-ins/animation-play/core/animation-celanimation.c
@@ -27,7 +27,6 @@
 
 #include "animation.h"
 #include "animation-camera.h"
-
 #include "animation-celanimation.h"
 
 typedef struct _AnimationCelAnimationPrivate AnimationCelAnimationPrivate;
@@ -35,41 +34,16 @@ typedef struct _AnimationCelAnimationPrivate AnimationCelAnimationPrivate;
 typedef struct
 {
   gchar *title;
-  /* The list of list of layers (identified by their tattoos). */
+  /* List of list of layers identified by tattoos. */
   GList *frames;
 }
 Track;
 
-typedef struct
-{
-  gint   tattoo;
-  gint   offset_x;
-  gint   offset_y;
-}
-CompLayer;
-
-typedef struct
-{
-  GeglBuffer *buffer;
-
-  /* The list of layers (identified by their tattoos) composited into
-   * this buffer allows to easily compare caches so that to not
-   * duplicate them.*/
-  CompLayer  *composition;
-  gint        n_sources;
-
-  gint        refs;
-}
-Cache;
-
 struct _AnimationCelAnimationPrivate
 {
   /* The number of frames. */
   gint             duration;
 
-  /* Frames are cached as GEGL buffers. */
-  GList           *cache;
-
   /* Panel comments. */
   GList           *comments;
 
@@ -111,30 +85,27 @@ typedef struct
                                      ANIMATION_TYPE_CEL_ANIMATION, \
                                      AnimationCelAnimationPrivate)
 
-static void         animation_cel_animation_finalize       (GObject           *object);
+static void         animation_cel_animation_finalize          (GObject      *object);
 
 /* Virtual methods */
 
-static gint         animation_cel_animation_get_duration   (Animation         *animation);
+static gint         animation_cel_animation_get_duration      (Animation    *animation);
 
-static GeglBuffer * animation_cel_animation_get_frame      (Animation         *animation,
-                                                            gint               pos);
+static gchar      * animation_cel_animation_get_frame_hash    (Animation    *animation,
+                                                               gint          position);
+static GeglBuffer * animation_cel_animation_create_frame      (Animation    *animation,
+                                                               GObject      *renderer,
+                                                               gint          position);
 
-static gboolean     animation_cel_animation_same           (Animation         *animation,
-                                                            gint               previous_pos,
-                                                            gint               next_pos);
+static void         animation_cel_animation_reset_defaults    (Animation    *animation);
+static gchar      * animation_cel_animation_serialize         (Animation    *animation,
+                                                               const gchar  *playback_xml);
+static gboolean     animation_cel_animation_deserialize       (Animation    *animation,
+                                                               const gchar  *xml,
+                                                               GError      **error);
 
-static void         animation_cel_animation_purge_cache    (Animation         *animation);
-
-static void         animation_cel_animation_reset_defaults (Animation         *animation);
-static gchar      * animation_cel_animation_serialize      (Animation         *animation,
-                                                            const gchar       *playback_xml);
-static gboolean     animation_cel_animation_deserialize    (Animation         *animation,
-                                                            const gchar       *xml,
-                                                            GError           **error);
-
-static void         animation_cel_animation_update_paint_view (Animation      *animation,
-                                                               gint            position);
+static void         animation_cel_animation_update_paint_view (Animation    *animation,
+                                                               gint          position);
 
 /* XML parsing */
 
@@ -163,12 +134,7 @@ static void      on_camera_offsets_changed                 (AnimationCamera
                                                             AnimationCelAnimation  *animation);
 /* Utils */
 
-static void         animation_cel_animation_cache          (AnimationCelAnimation  *animation,
-                                                            gint                    position);
 static void         animation_cel_animation_cleanup        (AnimationCelAnimation  *animation);
-static gboolean     animation_cel_animation_cache_cmp      (Cache                   *cache1,
-                                                            Cache                   *cache2);
-static void         animation_cel_animation_clean_cache    (Cache                   *cache);
 static void         animation_cel_animation_clean_track    (Track                   *track);
 
 G_DEFINE_TYPE (AnimationCelAnimation, animation_cel_animation, ANIMATION_TYPE_ANIMATION)
@@ -184,10 +150,9 @@ animation_cel_animation_class_init (AnimationCelAnimationClass *klass)
   object_class->finalize     = animation_cel_animation_finalize;
 
   anim_class->get_duration   = animation_cel_animation_get_duration;
-  anim_class->get_frame      = animation_cel_animation_get_frame;
-  anim_class->same           = animation_cel_animation_same;
 
-  anim_class->purge_cache    = animation_cel_animation_purge_cache;
+  anim_class->get_frame_hash = animation_cel_animation_get_frame_hash;
+  anim_class->create_frame   = animation_cel_animation_create_frame;
 
   anim_class->reset_defaults = animation_cel_animation_reset_defaults;
   anim_class->serialize      = animation_cel_animation_serialize;
@@ -223,39 +188,40 @@ animation_cel_animation_set_layers (AnimationCelAnimation *animation,
                                     const GList           *new_layers)
 {
   Track *track;
+  GList *layers;
 
   track = g_list_nth_data (animation->priv->tracks, level);
+  g_return_if_fail (track && position >= 0 &&
+                    position < animation->priv->duration);
 
-  if (track)
-    {
-      GList *layers = g_list_nth (track->frames, position);
+  layers = g_list_nth (track->frames, position);
 
-      if (! layers)
-        {
-          gint frames_length = g_list_length (track->frames);
-          gint i;
+  if (! layers)
+    {
+      gint frames_length = g_list_length (track->frames);
+      gint i;
 
-          track->frames = g_list_reverse (track->frames);
-          for (i = frames_length; i < position + 1; i++)
-            {
-              track->frames = g_list_prepend (track->frames, NULL);
-              layers = track->frames;
-            }
-          track->frames = g_list_reverse (track->frames);
-        }
-      /* Clean out previous layer list. */
-      g_list_free (layers->data);
-      if (new_layers)
+      track->frames = g_list_reverse (track->frames);
+      for (i = frames_length; i < position + 1; i++)
         {
-          layers->data = g_list_copy ((GList *) new_layers);
+          track->frames = g_list_prepend (track->frames, NULL);
+          layers = track->frames;
         }
-      else
-        {
-          layers->data = NULL;
-        }
-      animation_cel_animation_cache (animation, position);
-      g_signal_emit_by_name (animation, "cache-invalidated", position, 1);
+      track->frames = g_list_reverse (track->frames);
+    }
+
+  /* Clean out previous layer list. */
+  g_list_free (layers->data);
+  if (new_layers)
+    {
+      layers->data = g_list_copy ((GList *) new_layers);
+    }
+  else
+    {
+      layers->data = NULL;
     }
+
+  g_signal_emit_by_name (animation, "frames-changed", position, 1);
 }
 
 const GList *
@@ -263,17 +229,14 @@ animation_cel_animation_get_layers (AnimationCelAnimation *animation,
                                     gint                   level,
                                     gint                   position)
 {
-  GList *layers = NULL;
   Track *track;
 
   track = g_list_nth_data (animation->priv->tracks, level);
+  g_return_val_if_fail (track && position >= 0 &&
+                        position < animation->priv->duration,
+                        NULL);
 
-  if (track)
-    {
-      layers = g_list_nth_data (track->frames, position);
-    }
-
-  return layers;
+  return g_list_nth_data (track->frames, position);
 }
 
 void
@@ -328,14 +291,6 @@ animation_cel_animation_set_duration (AnimationCelAnimation *animation,
       GList *iter;
 
       /* Free memory. */
-      iter = g_list_nth (animation->priv->cache, duration);
-      if (iter && iter->prev)
-        {
-          iter->prev->next = NULL;
-          iter->prev = NULL;
-        }
-      g_list_free_full (iter, (GDestroyNotify) animation_cel_animation_clean_cache);
-
       iter = g_list_nth (animation->priv->tracks, duration);
       if (iter && iter->prev)
         {
@@ -391,46 +346,39 @@ gint
 animation_cel_animation_level_up (AnimationCelAnimation *animation,
                                   gint                   level)
 {
-  gint tracks_n = g_list_length (animation->priv->tracks);
+  GList *track;
+  GList *prev_track;
+  GList *next_track;
+  GList *iter;
+  gint   i;
 
-  g_return_val_if_fail (level >= 0 && level < tracks_n, level);
+  g_return_val_if_fail (level >= 0 &&
+                        level < g_list_length (animation->priv->tracks) - 1,
+                        level);
 
-  if (level < tracks_n - 1)
+  track = g_list_nth (animation->priv->tracks, level);
+  prev_track = track->prev;
+  next_track = track->next;
+
+  if (prev_track)
+    prev_track->next = next_track;
+  else
+    animation->priv->tracks = next_track;
+  next_track->prev = prev_track;
+  track->prev      = next_track;
+  track->next      = next_track->next;
+  next_track->next = track;
+
+  level++;
+
+  iter  = ((Track *) track->data)->frames;
+  for (i = 0; iter; iter = iter->next, i++)
     {
-      GList *item = g_list_nth (animation->priv->tracks, level);
-      GList *prev = item->prev;
-      GList *next = item->next;
-      Track *track;
-      GList *iter;
-      gint   i;
-
-      if (prev)
-        prev->next = next;
-      else
-        animation->priv->tracks = next;
-      next->prev = prev;
-      item->prev = next;
-      item->next = next->next;
-      next->next = item;
-
-      level++;
-
-      track = item->data;
-      iter  = track->frames;
-      for (i = 0; iter; iter = iter->next, i++)
+      if (iter->data)
         {
-          g_signal_emit_by_name (animation, "loading",
-                                 (gdouble) i / ((gdouble) animation->priv->duration - 0.999));
-
-          if (iter->data)
-            {
-              /* Only cache if the track had contents for this frame. */
-              animation_cel_animation_cache (animation, i);
-            }
+          /* Only cache if the track had contents for this frame. */
+          g_signal_emit_by_name (animation, "frames-changed", i, 1);
         }
-      g_signal_emit_by_name (animation, "cache-invalidated",
-                             0, g_list_length (track->frames));
-      g_signal_emit_by_name (animation, "loaded");
     }
 
   return level;
@@ -440,46 +388,39 @@ gint
 animation_cel_animation_level_down (AnimationCelAnimation *animation,
                                     gint                   level)
 {
-  gint tracks_n = g_list_length (animation->priv->tracks);
+  GList *track;
+  GList *prev_track;
+  GList *next_track;
+  GList *iter;
+  gint   i;
 
-  g_return_val_if_fail (level >= 0 && level < tracks_n, level);
+  g_return_val_if_fail (level > 0 &&
+                        level < g_list_length (animation->priv->tracks),
+                        level);
 
-  if (level > 0)
+  track = g_list_nth (animation->priv->tracks, level);
+  prev_track = track->prev;
+  next_track = track->next;
+
+  if (! prev_track->prev)
+    animation->priv->tracks = track;
+  if (next_track)
+    next_track->prev = prev_track;
+  prev_track->next = next_track;
+  track->next = prev_track;
+  track->prev = prev_track->prev;
+  prev_track->prev = track;
+
+  level--;
+
+  iter  = ((Track *) track->data)->frames;
+  for (i = 0; iter; iter = iter->next, i++)
     {
-      GList *item = g_list_nth (animation->priv->tracks, level);
-      GList *prev = item->prev;
-      GList *next = item->next;
-      Track *track;
-      GList *iter;
-      gint   i;
-
-      if (! prev->prev)
-        animation->priv->tracks = item;
-      if (next)
-        next->prev = prev;
-      prev->next = next;
-      item->next = prev;
-      item->prev = prev->prev;
-      prev->prev = item;
-
-      level--;
-
-      track = item->data;
-      iter  = track->frames;
-      for (i = 0; iter; iter = iter->next, i++)
+      if (iter->data)
         {
-          g_signal_emit_by_name (animation, "loading",
-                                 (gdouble) i / ((gdouble) animation->priv->duration - 0.999));
-
-          if (iter->data)
-            {
-              /* Only cache if the track had contents for this frame. */
-              animation_cel_animation_cache (animation, i);
-            }
+          /* Only cache if the track had contents for this frame. */
+          g_signal_emit_by_name (animation, "frames-changed", i, 1);
         }
-      g_signal_emit_by_name (animation, "cache-invalidated",
-                             0, g_list_length (track->frames));
-      g_signal_emit_by_name (animation, "loaded");
     }
 
   return level;
@@ -489,36 +430,31 @@ gboolean
 animation_cel_animation_level_delete (AnimationCelAnimation *animation,
                                       gint                   level)
 {
-  gint   tracks_n = g_list_length (animation->priv->tracks);
-  GList *item;
-  GList *iter;
-  Track *track;
-  gint   i;
+  gint tracks_n = g_list_length (animation->priv->tracks);
 
   g_return_val_if_fail (level >= 0 && level < tracks_n, FALSE);
 
   /* Do not remove when there is only a single level. */
   if (tracks_n > 1)
     {
+      Track *track;
+      GList *item;
+      GList *iter;
+      gint   i;
+
       item = g_list_nth (animation->priv->tracks, level);
       track = item->data;
-      animation->priv->tracks = g_list_delete_link (animation->priv->tracks, item);
-
+      animation->priv->tracks = g_list_delete_link (animation->priv->tracks,
+                                                    item);
       iter = track->frames;
       for (i = 0; iter; iter = iter->next, i++)
         {
-          g_signal_emit_by_name (animation, "loading",
-                                 (gdouble) i / ((gdouble) animation->priv->duration - 0.999));
-
           if (iter->data)
             {
               /* Only cache if the track had contents for this frame. */
-              animation_cel_animation_cache (animation, i);
+              g_signal_emit_by_name (animation, "frames-changed", i, 1);
             }
         }
-      g_signal_emit_by_name (animation, "cache-invalidated",
-                             0, g_list_length (track->frames));
-      g_signal_emit_by_name (animation, "loaded");
       animation_cel_animation_clean_track (track);
 
       return TRUE;
@@ -600,15 +536,8 @@ animation_cel_animation_cel_delete (AnimationCelAnimation *animation,
 
           for (i = position; iter; iter = iter->next, i++)
             {
-              g_signal_emit_by_name (animation, "loading",
-                                     (gdouble) i / ((gdouble) animation->priv->duration - 0.999));
-
-              animation_cel_animation_cache (animation, i);
+              g_signal_emit_by_name (animation, "frames-changed", i, 1);
             }
-          if (i > position)
-            g_signal_emit_by_name (animation, "cache-invalidated",
-                                   position, i - position);
-          g_signal_emit_by_name (animation, "loaded");
 
           return TRUE;
         }
@@ -653,16 +582,8 @@ animation_cel_animation_cel_add (AnimationCelAnimation *animation,
         {
           for (; cel; cel = cel->next, i++)
             {
-              g_signal_emit_by_name (animation, "loading",
-                                     (gdouble) i / ((gdouble) animation->priv->duration - 0.999));
-
-              animation_cel_animation_cache (animation, i);
+              g_signal_emit_by_name (animation, "frames-changed", i, 1);
             }
-          if (i > position)
-            g_signal_emit_by_name (animation, "cache-invalidated",
-                                   position, i - position);
-          g_signal_emit_by_name (animation, "loaded");
-
           return TRUE;
         }
     }
@@ -677,63 +598,120 @@ animation_cel_animation_get_duration (Animation *animation)
   return ANIMATION_CEL_ANIMATION (animation)->priv->duration;
 }
 
-static GeglBuffer *
-animation_cel_animation_get_frame (Animation *animation,
-                                   gint       pos)
+static gchar *
+animation_cel_animation_get_frame_hash (Animation *animation,
+                                        gint       position)
 {
   AnimationCelAnimation *cel_animation;
-  Cache                 *cache;
-  GeglBuffer            *frame = NULL;
+  gchar                 *hash = g_strdup ("");
+  GList                 *iter;
+  gint                   main_offset_x;
+  gint                   main_offset_y;
 
   cel_animation = ANIMATION_CEL_ANIMATION (animation);
+  animation_camera_get (cel_animation->priv->camera,
+                        position, &main_offset_x, &main_offset_y);
+
+  /* Create the new buffer layer composition. */
+  for (iter = cel_animation->priv->tracks; iter; iter = iter->next)
+    {
+      Track *track = iter->data;
+      GList *layers;
+      GList *layer;
 
-  cache = g_list_nth_data (cel_animation->priv->cache,
-                           pos);
-  if (cache)
+      layers = g_list_nth_data (track->frames, position);
+
+      for (layer = layers; layer; layer = layer->next)
+        {
+          gint tattoo;
+
+          tattoo = GPOINTER_TO_INT (layer->data);
+          if (tattoo)
+            {
+              gchar *tmp = hash;
+              hash = g_strdup_printf ("%s[%d,%d]%d;",
+                                      hash,
+                                      main_offset_x, main_offset_y,
+                                      tattoo);
+              g_free (tmp);
+            }
+        }
+    }
+  if (strlen (hash) == 0)
     {
-      frame = g_object_ref (cache->buffer);
+      g_free (hash);
+      hash = NULL;
     }
-  return frame;
+  return hash;
+
 }
 
-static gboolean
-animation_cel_animation_same (Animation *animation,
-                              gint       pos1,
-                              gint       pos2)
+static GeglBuffer *
+animation_cel_animation_create_frame (Animation *animation,
+                                      GObject   *renderer G_GNUC_UNUSED,
+                                      gint       position)
 {
   AnimationCelAnimation *cel_animation;
-  Cache                 *cache1;
-  Cache                 *cache2;
+  GeglBuffer            *buffer = NULL;
+  GList                 *iter;
+  gint32                 image_id;
+  gdouble                proxy_ratio;
+  gint                   preview_width;
+  gint                   preview_height;
+  gint                   offset_x;
+  gint                   offset_y;
 
   cel_animation = ANIMATION_CEL_ANIMATION (animation);
+  image_id = animation_get_image_id (animation);
+  proxy_ratio = animation_get_proxy (animation);
+  animation_get_size (animation,
+                      &preview_width, &preview_height);
+  animation_camera_get (cel_animation->priv->camera,
+                        position, &offset_x, &offset_y);
 
-  g_return_val_if_fail (pos1 >= 0                            &&
-                        pos1 < cel_animation->priv->duration &&
-                        pos2 >= 0                            &&
-                        pos2 < cel_animation->priv->duration,
-                        FALSE);
+  for (iter = cel_animation->priv->tracks; iter; iter = iter->next)
+    {
+      Track *track = iter->data;
+      GList *layers;
+      GList *iter2;
 
-  cache1 = g_list_nth_data (cel_animation->priv->cache, pos1);
-  cache2 = g_list_nth_data (cel_animation->priv->cache, pos2);
+      layers = g_list_nth_data (track->frames, position);
 
-  return animation_cel_animation_cache_cmp (cache1, cache2);
-}
+      for (iter2 = layers; iter2; iter2 = iter2->next)
+        {
+          GeglBuffer *source = NULL;
+          GeglBuffer *intermediate;
+          gint        layer_offx;
+          gint        layer_offy;
+          gint32      layer;
+          gint        tattoo;
 
-static void
-animation_cel_animation_purge_cache (Animation *animation)
-{
-  gint duration;
-  gint i;
+          tattoo = GPOINTER_TO_INT (iter2->data);
 
-  duration = animation_get_duration (animation);
-  for (i = 0; i < duration; i++)
-    {
-      animation_cel_animation_cache (ANIMATION_CEL_ANIMATION (animation),
-                                     i);
-      g_signal_emit_by_name (animation, "loading",
-                             (gdouble) i / ((gdouble) duration - 0.999));
+          layer = gimp_image_get_layer_by_tattoo (image_id, tattoo);
+          if (layer > 0)
+            source = gimp_drawable_get_buffer (layer);
+          if (layer <= 0 || ! source)
+            {
+              g_printerr ("Warning: a layer used for frame %d has been deleted.\n",
+                          position);
+              continue;
+            }
+          gimp_drawable_offsets (layer, &layer_offx, &layer_offy);
+          intermediate = normal_blend (preview_width, preview_height,
+                                       buffer, 1.0, 0, 0,
+                                       source, proxy_ratio,
+                                       layer_offx + offset_x,
+                                       layer_offy + offset_y);
+          g_object_unref (source);
+          if (buffer)
+            {
+              g_object_unref (buffer);
+            }
+          buffer = intermediate;
+        }
     }
-  g_signal_emit_by_name (animation, "loaded");
+  return buffer;
 }
 
 static void
@@ -808,7 +786,7 @@ animation_cel_animation_serialize (Animation   *animation,
     {
       Track *track = iter->data;
       GList *iter2;
-      gint   pos;
+      gint   position;
       gint   duration;
 
       xml2 = g_markup_printf_escaped ("<sequence name=\"%s\">",
@@ -818,7 +796,7 @@ animation_cel_animation_serialize (Animation   *animation,
       g_free (tmp);
       g_free (xml2);
 
-      pos = 1;
+      position = 1;
       duration = 0;
       for (iter2 = track->frames; iter2; iter2 = iter2->next)
         {
@@ -851,7 +829,8 @@ animation_cel_animation_serialize (Animation   *animation,
                   /* Open tag. */
                   xml2 = g_markup_printf_escaped ("<frame position=\"%d\""
                                                   " duration=\"%d\">",
-                                                  pos - duration, duration);
+                                                  position - duration,
+                                                  duration);
                   tmp = xml;
                   xml = g_strconcat (xml, xml2, NULL);
                   g_free (tmp);
@@ -879,7 +858,7 @@ animation_cel_animation_serialize (Animation   *animation,
                   duration = 0;
                 }
             }
-          pos++;
+          position++;
         }
 
       tmp = xml;
@@ -956,6 +935,8 @@ animation_cel_animation_deserialize (Animation    *animation,
       g_signal_connect (cel_animation->priv->camera, "offsets-changed",
                         G_CALLBACK (on_camera_offsets_changed),
                         animation);
+      g_signal_emit_by_name (animation, "frames-changed", 0,
+                             cel_animation->priv->duration);
     }
   g_markup_parse_context_free (context);
 
@@ -967,12 +948,13 @@ animation_cel_animation_update_paint_view (Animation *animation,
                                            gint       position)
 {
   AnimationCelAnimation *cel_animation;
-  Cache                 *cache;
   gint                  *layers;
+  GList                 *iter;
   gint                   num_layers;
   gint32                 image_id;
   gint                   i;
 
+  cel_animation = ANIMATION_CEL_ANIMATION (animation);
   image_id = animation_get_image_id (animation);
 
   /* Hide all layers. */
@@ -983,21 +965,21 @@ animation_cel_animation_update_paint_view (Animation *animation,
     }
 
   /* Show layers */
-  cel_animation = ANIMATION_CEL_ANIMATION (animation);
-
-  cache = g_list_nth_data (cel_animation->priv->cache,
-                           position);
-  if (cache)
+  for (iter = cel_animation->priv->tracks; iter; iter = iter->next)
     {
-      gint i;
+      Track *track = iter->data;
+      GList *frame_layers;
+      GList *iter2;
 
-      for (i = 0; i < cache->n_sources; i++)
+      frame_layers = g_list_nth_data (track->frames, position);
+
+      for (iter2 = frame_layers; iter2; iter2 = iter2->next)
         {
-          gint tattoo = cache->composition[i].tattoo;
+          gint tattoo;
           gint layer;
 
+          tattoo = GPOINTER_TO_INT (iter2->data);
           layer = gimp_image_get_layer_by_tattoo (image_id, tattoo);
-
           show_item (layer, GIMP_COLOR_TAG_RED);
         }
     }
@@ -1310,191 +1292,15 @@ on_camera_offsets_changed (AnimationCamera       *camera,
                            gint                   duration,
                            AnimationCelAnimation *animation)
 {
-  gint i;
-
-  for (i = position; i < position + duration; i++)
-    animation_cel_animation_cache (animation, i);
-
-  g_signal_emit_by_name (animation, "cache-invalidated",
+  g_signal_emit_by_name (animation, "frames-changed",
                          position, duration);
 }
 
 /**** Utils ****/
 
 static void
-animation_cel_animation_cache (AnimationCelAnimation *animation,
-                               gint                   pos)
-{
-  GeglBuffer *backdrop = NULL;
-  GList      *iter;
-  Cache      *cache;
-  CompLayer  *composition;
-  gint        n_sources = 0;
-  gint32      image_id;
-  gdouble     proxy_ratio;
-  gint        preview_width;
-  gint        preview_height;
-  gint        i;
-  gint        main_offset_x;
-  gint        main_offset_y;
-
-  /* Clean out current cache. */
-  iter = g_list_nth (animation->priv->cache, pos);
-  if (iter && iter->data)
-    {
-      Cache *cache = iter->data;
-
-      if (--(cache->refs) == 0)
-        {
-          g_free (cache->composition);
-          g_object_unref (cache->buffer);
-          g_free (cache);
-        }
-      iter->data = NULL;
-    }
-
-  /* Check if new configuration needs caching. */
-  for (iter = animation->priv->tracks; iter; iter = iter->next)
-    {
-      Track *track = iter->data;
-      GList *layers;
-
-      layers = g_list_nth_data (track->frames, pos);
-
-      n_sources += g_list_length (layers);
-    }
-  if (n_sources == 0)
-    {
-      return;
-    }
-
-  /* Make sure the cache list is long enough. */
-  if (pos >= g_list_length (animation->priv->cache))
-    {
-      animation->priv->cache = g_list_reverse (animation->priv->cache);
-      for (i = g_list_length (animation->priv->cache); i <= pos; i++)
-        {
-          animation->priv->cache = g_list_prepend (animation->priv->cache,
-                                                   NULL);
-        }
-      animation->priv->cache = g_list_reverse (animation->priv->cache);
-    }
-
-  animation_camera_get (animation->priv->camera,
-                        pos, &main_offset_x, &main_offset_y);
-
-  /* Create the new buffer composition. */
-  composition = g_new0 (CompLayer, n_sources);
-  i = 0;
-  for (iter = animation->priv->tracks; iter; iter = iter->next)
-    {
-      Track *track = iter->data;
-      GList *layers;
-      GList *layer;
-
-      layers = g_list_nth_data (track->frames, pos);
-
-      for (layer = layers; layer; layer = layer->next)
-        {
-          gint tattoo;
-
-          tattoo = GPOINTER_TO_INT (layer->data);
-          if (tattoo)
-            {
-              composition[i].tattoo = tattoo;
-              composition[i].offset_x = main_offset_x;
-              composition[i++].offset_y = main_offset_y;
-            }
-        }
-    }
-
-  /* Check if new configuration was not already cached. */
-  for (iter = animation->priv->cache; iter; iter = iter->next)
-    {
-      if (iter->data)
-        {
-          Cache    *cache = iter->data;
-          gboolean  same = FALSE;
-
-          if (n_sources == cache->n_sources)
-            {
-              same = TRUE;
-              for (i = 0; i < n_sources; i++)
-                {
-                  if (cache->composition[i].tattoo   != composition[i].tattoo   ||
-                      cache->composition[i].offset_x != composition[i].offset_x ||
-                      cache->composition[i].offset_y != composition[i].offset_y)
-                    {
-                      same = FALSE;
-                      break;
-                    }
-                }
-              if (same)
-                {
-                  /* A buffer with the same contents already exists. */
-                  g_free (composition);
-                  (cache->refs)++;
-                  g_list_nth (animation->priv->cache, pos)->data = cache;
-                  return;
-                }
-            }
-        }
-    }
-
-  /* New configuration. Finally compute the cache. */
-  cache = g_new0 (Cache, 1);
-  cache->refs        = 1;
-  cache->n_sources   = n_sources;
-  cache->composition = composition;
-
-  image_id    = animation_get_image_id (ANIMATION (animation));
-  proxy_ratio = animation_get_proxy (ANIMATION (animation));
-  animation_get_size (ANIMATION (animation),
-                      &preview_width, &preview_height);
-
-  for (i = 0; i < n_sources; i++)
-    {
-      GeglBuffer *source;
-      GeglBuffer *intermediate;
-      gint32      layer;
-      gint        layer_offx;
-      gint        layer_offy;
-
-      layer = gimp_image_get_layer_by_tattoo (image_id,
-                                              cache->composition[i].tattoo);
-      if (layer > 0)
-        source = gimp_drawable_get_buffer (layer);
-      if (layer <= 0 || ! source)
-        {
-          g_printerr ("Warning: a layer used for frame %d has been deleted.\n",
-                      pos);
-          continue;
-        }
-      gimp_drawable_offsets (layer, &layer_offx, &layer_offy);
-      intermediate = normal_blend (preview_width, preview_height,
-                                   backdrop, 1.0, 0, 0,
-                                   source, proxy_ratio,
-                                   layer_offx + cache->composition[i].offset_x,
-                                   layer_offy + cache->composition[i].offset_y);
-      g_object_unref (source);
-      if (backdrop)
-        {
-          g_object_unref (backdrop);
-        }
-      backdrop = intermediate;
-    }
-  cache->buffer = backdrop;
-
-  /* This item exists and has a NULL data. */
-  g_list_nth (animation->priv->cache, pos)->data = cache;
-}
-
-static void
 animation_cel_animation_cleanup (AnimationCelAnimation *animation)
 {
-  g_list_free_full (animation->priv->cache,
-                    (GDestroyNotify) animation_cel_animation_clean_cache);
-  animation->priv->cache    = NULL;
   g_list_free_full (animation->priv->comments,
                     (GDestroyNotify) g_free);
   animation->priv->comments = NULL;
@@ -1505,43 +1311,6 @@ animation_cel_animation_cleanup (AnimationCelAnimation *animation)
   g_object_unref (animation->priv->camera);
 }
 
-static gboolean
-animation_cel_animation_cache_cmp (Cache *cache1,
-                                   Cache *cache2)
-{
-  gboolean identical = FALSE;
-
-  if (cache1 && cache2 &&
-      cache1->n_sources == cache2->n_sources)
-    {
-      gint i;
-
-      identical = TRUE;
-      for (i = 0; i < cache1->n_sources; i++)
-        {
-          if (cache1->composition[i].tattoo   != cache2->composition[i].tattoo   ||
-              cache1->composition[i].offset_x != cache2->composition[i].offset_x ||
-              cache1->composition[i].offset_y != cache2->composition[i].offset_y)
-            {
-              identical = FALSE;
-              break;
-            }
-        }
-    }
-  return identical;
-}
-
-static void
-animation_cel_animation_clean_cache (Cache *cache)
-{
-  if (cache != NULL && --(cache->refs) == 0)
-    {
-      g_object_unref (cache->buffer);
-      g_free (cache->composition);
-      g_free (cache);
-    }
-}
-
 static void
 animation_cel_animation_clean_track (Track *track)
 {
diff --git a/plug-ins/animation-play/core/animation-playback.c 
b/plug-ins/animation-play/core/animation-playback.c
index 50ca135..b4b2da3 100644
--- a/plug-ins/animation-play/core/animation-playback.c
+++ b/plug-ins/animation-play/core/animation-playback.c
@@ -27,6 +27,7 @@
 
 #include "animation.h"
 #include "animation-playback.h"
+#include "animation-renderer.h"
 
 enum
 {
@@ -47,6 +48,7 @@ enum
 struct _AnimationPlaybackPrivate
 {
   Animation   *animation;
+  GObject     *renderer;
 
   /* State of the currently loaded playback. */
   gint         position;
@@ -67,11 +69,6 @@ typedef struct
   gint               level;
 } ParseStatus;
 
-#define ANIMATION_PLAYBACK_GET_PRIVATE(playback) \
-        G_TYPE_INSTANCE_GET_PRIVATE (playback, \
-                                     ANIMATION_PLAYBACK_TYPE_ANIMATION_PLAYBACK, \
-                                     AnimationPlaybackPrivate)
-
 static void       animation_playback_finalize               (GObject      *object);
 static void       animation_playback_set_property           (GObject           *object,
                                                              guint              property_id,
@@ -85,9 +82,8 @@ static void       animation_playback_get_property           (GObject           *
 static void       on_duration_changed                       (Animation         *animation,
                                                              gint               duration,
                                                              AnimationPlayback *playback);
-static void       on_cache_invalidated                      (Animation         *animation,
+static void       on_cache_updated                          (AnimationRenderer *renderer,
                                                              gint               position,
-                                                             gint               length,
                                                              AnimationPlayback *playback);
 
 /* Timer callback for playback. */
@@ -312,6 +308,16 @@ animation_playback_get_position (AnimationPlayback *playback)
   return playback->priv->position;
 }
 
+GeglBuffer *
+animation_playback_get_buffer (AnimationPlayback *playback,
+                               gint               position)
+{
+  AnimationRenderer *renderer;
+
+  renderer = ANIMATION_RENDERER (playback->priv->renderer);
+  return animation_renderer_get_buffer (renderer, position);
+}
+
 gboolean
 animation_playback_is_playing (AnimationPlayback *playback)
 {
@@ -360,10 +366,10 @@ animation_playback_stop (AnimationPlayback *playback)
 void
 animation_playback_next (AnimationPlayback *playback)
 {
-  Animation  *animation    = playback->priv->animation;
-  GeglBuffer *buffer       = NULL;
-  gint        previous_pos = playback->priv->position;
-  gboolean    identical;
+  AnimationRenderer *renderer;
+  GeglBuffer        *buffer       = NULL;
+  gint               previous_pos = playback->priv->position;
+  gboolean           identical;
 
   if (! playback->priv->animation)
     return;
@@ -372,12 +378,14 @@ animation_playback_next (AnimationPlayback *playback)
                              ((playback->priv->position - playback->priv->start + 1) %
                               (playback->priv->stop - playback->priv->start + 1));
 
-  identical = ANIMATION_GET_CLASS (animation)->same (animation,
-                                                     previous_pos,
-                                                     playback->priv->position);
+  renderer = ANIMATION_RENDERER (playback->priv->renderer);
+  identical = animation_renderer_identical (renderer,
+                                            previous_pos,
+                                            playback->priv->position);
   if (! identical)
     {
-      buffer = animation_get_frame (animation, playback->priv->position);
+      buffer = animation_renderer_get_buffer (renderer,
+                                              playback->priv->position);
     }
   g_signal_emit (playback, animation_playback_signals[RENDER], 0,
                  playback->priv->position, buffer, ! identical);
@@ -390,10 +398,10 @@ animation_playback_next (AnimationPlayback *playback)
 void
 animation_playback_prev (AnimationPlayback *playback)
 {
-  Animation  *animation = playback->priv->animation;
-  GeglBuffer *buffer    = NULL;
-  gint        prev_pos  = playback->priv->position;
-  gboolean    identical;
+  AnimationRenderer *renderer;
+  GeglBuffer        *buffer    = NULL;
+  gint               prev_pos  = playback->priv->position;
+  gboolean           identical;
 
   if (! playback->priv->animation)
     return;
@@ -407,12 +415,13 @@ animation_playback_prev (AnimationPlayback *playback)
       --playback->priv->position;
     }
 
-  identical = ANIMATION_GET_CLASS (animation)->same (animation,
-                                                     prev_pos,
-                                                     playback->priv->position);
+  renderer = ANIMATION_RENDERER (playback->priv->renderer);
+  identical = animation_renderer_identical (renderer,
+                                            prev_pos,
+                                            playback->priv->position);
   if (! identical)
     {
-      buffer = animation_get_frame (animation, playback->priv->position);
+      buffer = animation_renderer_get_buffer (renderer, playback->priv->position);
     }
   g_signal_emit (playback, animation_playback_signals[RENDER], 0,
                  playback->priv->position, buffer, ! identical);
@@ -424,10 +433,10 @@ void
 animation_playback_jump (AnimationPlayback *playback,
                          gint               index)
 {
-  Animation  *animation = playback->priv->animation;
-  GeglBuffer *buffer    = NULL;
-  gint        prev_pos  = playback->priv->position;
-  gboolean    identical;
+  AnimationRenderer *renderer;
+  GeglBuffer        *buffer    = NULL;
+  gint               prev_pos  = playback->priv->position;
+  gboolean           identical;
 
   if (! playback->priv->animation)
     return;
@@ -438,12 +447,13 @@ animation_playback_jump (AnimationPlayback *playback,
   else
     playback->priv->position = index;
 
-  identical = ANIMATION_GET_CLASS (animation)->same (animation,
-                                                     prev_pos,
-                                                     playback->priv->position);
+  renderer = ANIMATION_RENDERER (playback->priv->renderer);
+  identical = animation_renderer_identical (renderer,
+                                            prev_pos,
+                                            playback->priv->position);
   if (! identical)
     {
-      buffer = animation_get_frame (animation, playback->priv->position);
+      buffer = animation_renderer_get_buffer (renderer, playback->priv->position);
     }
   g_signal_emit (playback, animation_playback_signals[RENDER], 0,
                  playback->priv->position, buffer, ! identical);
@@ -545,6 +555,7 @@ animation_playback_finalize (GObject      *object)
 {
   AnimationPlayback *playback = ANIMATION_PLAYBACK (object);
 
+  g_object_unref (playback->priv->renderer);
   if (playback->priv->animation)
     g_object_unref (playback->priv->animation);
 
@@ -567,9 +578,12 @@ animation_playback_set_property (GObject      *object,
 
           if (playback->priv->animation)
             g_object_unref (playback->priv->animation);
+          if (playback->priv->renderer)
+            g_object_unref (playback->priv->renderer);
 
           animation = g_value_dup_object (value);
           playback->priv->animation = animation;
+          playback->priv->renderer = NULL;
 
           if (! animation)
             break;
@@ -580,10 +594,12 @@ animation_playback_set_property (GObject      *object,
           playback->priv->stop  = animation_get_duration (animation) - 1;
           playback->priv->stop_at_end = TRUE;
 
-          g_signal_connect (animation, "cache-invalidated",
-                            G_CALLBACK (on_cache_invalidated), playback);
           g_signal_connect (animation, "duration-changed",
                             G_CALLBACK (on_duration_changed), playback);
+
+          playback->priv->renderer = animation_renderer_new (object);
+          g_signal_connect (playback->priv->renderer, "cache-updated",
+                            G_CALLBACK (on_cache_updated), playback);
         }
       break;
 
@@ -643,23 +659,19 @@ on_duration_changed (Animation         *animation,
 }
 
 static void
-on_cache_invalidated (Animation         *animation,
-                      gint               position,
-                      gint               length,
-                      AnimationPlayback *playback)
+on_cache_updated (AnimationRenderer *renderer,
+                  gint               position,
+                  AnimationPlayback *playback)
 {
-  gint cur_position;
-
-  cur_position = animation_playback_get_position (playback);
-
-  if (cur_position >= position &&
-      cur_position < position + length)
+  if (animation_playback_get_position (playback) == position)
     {
-      GeglBuffer *buffer;
+      AnimationRenderer *renderer;
+      GeglBuffer        *buffer;
 
-      buffer = animation_get_frame (animation, cur_position);
-      g_signal_emit_by_name (playback, "render",
-                             cur_position, buffer, TRUE);
+      renderer = ANIMATION_RENDERER (playback->priv->renderer);
+      buffer = animation_renderer_get_buffer (renderer, position);
+      g_signal_emit (playback, animation_playback_signals[RENDER], 0,
+                     position, buffer, TRUE);
       if (buffer)
         {
           g_object_unref (buffer);
diff --git a/plug-ins/animation-play/core/animation-playback.h 
b/plug-ins/animation-play/core/animation-playback.h
index 08d59ff..145ac57 100644
--- a/plug-ins/animation-play/core/animation-playback.h
+++ b/plug-ins/animation-play/core/animation-playback.h
@@ -68,6 +68,8 @@ void          animation_playback_set_animation (AnimationPlayback   *playback,
 Animation   * animation_playback_get_animation (AnimationPlayback   *playback);
 
 gint          animation_playback_get_position  (AnimationPlayback   *playback);
+GeglBuffer  * animation_playback_get_buffer    (AnimationPlayback   *playback,
+                                                gint                 position);
 
 gboolean      animation_playback_is_playing    (AnimationPlayback   *playback);
 void          animation_playback_play          (AnimationPlayback   *playback);
diff --git a/plug-ins/animation-play/core/animation-renderer.c 
b/plug-ins/animation-play/core/animation-renderer.c
new file mode 100644
index 0000000..272439e
--- /dev/null
+++ b/plug-ins/animation-play/core/animation-renderer.c
@@ -0,0 +1,355 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation-renderer.c
+ * Copyright (C) 2017 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/stdplugins-intl.h>
+
+#include "animation.h"
+#include "animation-playback.h"
+#include "animation-renderer.h"
+
+enum
+{
+  CACHE_UPDATED,
+  LAST_SIGNAL
+};
+enum
+{
+  PROP_0,
+  PROP_PLAYBACK
+};
+
+struct _AnimationRendererPrivate
+{
+  AnimationPlayback  *playback;
+
+  /* Frames are cached as GEGL buffers. */
+  GeglBuffer        **cache;
+  gchar             **hashes;
+  GHashTable         *cache_table;
+  gint                cache_size;
+};
+
+static void     animation_renderer_finalize     (GObject           *object);
+static void     animation_renderer_set_property (GObject           *object,
+                                                 guint              property_id,
+                                                 const GValue      *value,
+                                                 GParamSpec        *pspec);
+static void     animation_renderer_get_property (GObject           *object,
+                                                 guint              property_id,
+                                                 GValue            *value,
+                                                 GParamSpec        *pspec);
+
+static void     on_frames_changed               (Animation         *animation,
+                                                 gint               position,
+                                                 gint               length,
+                                                 AnimationRenderer *renderer);
+static void     on_duration_changed             (Animation         *animation,
+                                                 gint               duration,
+                                                 AnimationRenderer *renderer);
+
+G_DEFINE_TYPE (AnimationRenderer, animation_renderer, G_TYPE_OBJECT)
+
+#define parent_class animation_renderer_parent_class
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+animation_renderer_class_init (AnimationRendererClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  /**
+   * Animation::cache-updated:
+   * @animation: the animation.
+   * @position: the frame position whose cache was updated.
+   *
+   * The ::cache-updated signal will be emitted when the contents
+   * of frame at @position changes.
+   */
+  signals[CACHE_UPDATED] =
+    g_signal_new ("cache-updated",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  G_STRUCT_OFFSET (AnimationRendererClass, cache_updated),
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_INT);
+
+  object_class->finalize     = animation_renderer_finalize;
+  object_class->set_property = animation_renderer_set_property;
+  object_class->get_property = animation_renderer_get_property;
+
+  /**
+   * AnimationRenderer:animation:
+   *
+   * The associated #Animation.
+   */
+  g_object_class_install_property (object_class, PROP_PLAYBACK,
+                                   g_param_spec_object ("playback",
+                                                        NULL, NULL,
+                                                        ANIMATION_TYPE_PLAYBACK,
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT_ONLY));
+
+  g_type_class_add_private (klass, sizeof (AnimationRendererPrivate));
+}
+
+static void
+animation_renderer_init (AnimationRenderer *renderer)
+{
+  renderer->priv = G_TYPE_INSTANCE_GET_PRIVATE (renderer,
+                                                ANIMATION_TYPE_RENDERER,
+                                                AnimationRendererPrivate);
+  renderer->priv->cache_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                       (GDestroyNotify) g_free,
+                                                       (GDestroyNotify) g_weak_ref_clear);
+}
+
+static void
+animation_renderer_finalize (GObject *object)
+{
+  AnimationRenderer *renderer = ANIMATION_RENDERER (object);
+  Animation         *animation;
+  gint               i;
+
+  animation = animation_playback_get_animation (renderer->priv->playback);
+
+  for (i = 0; i < animation_get_duration (animation); i++)
+    {
+      if (renderer->priv->cache[i])
+        g_object_unref (renderer->priv->cache[i]);
+      if (renderer->priv->hashes[i])
+        g_free (renderer->priv->hashes[i]);
+    }
+  g_free (renderer->priv->cache);
+  g_free (renderer->priv->hashes);
+  g_hash_table_destroy (renderer->priv->cache_table);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+animation_renderer_set_property (GObject      *object,
+                                 guint         property_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  AnimationRenderer *renderer = ANIMATION_RENDERER (object);
+
+  switch (property_id)
+    {
+    case PROP_PLAYBACK:
+      renderer->priv->playback = g_value_get_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+animation_renderer_get_property (GObject      *object,
+                                 guint         property_id,
+                                 GValue       *value,
+                                 GParamSpec   *pspec)
+{
+  AnimationRenderer *renderer = ANIMATION_RENDERER (object);
+
+  switch (property_id)
+    {
+    case PROP_PLAYBACK:
+      g_value_set_object (value, renderer->priv->playback);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+on_frames_changed (Animation         *animation,
+                   gint               position,
+                   gint               length,
+                   AnimationRenderer *renderer)
+{
+  gint i;
+
+  for (i = position; i < position + length; i++)
+    {
+      GeglBuffer *buffer  = NULL;
+      GWeakRef   *ref     = NULL;
+      gchar      *hash    = NULL;
+      gchar      *hash_cp = NULL;
+
+      hash = ANIMATION_GET_CLASS (animation)->get_frame_hash (animation, i);
+      if (hash)
+        {
+          ref = g_hash_table_lookup (renderer->priv->cache_table, hash);
+          hash_cp = g_strdup (hash);
+          if (ref)
+            {
+              /* Acquire and add a new reference to the buffer. */
+              buffer = g_weak_ref_get (ref);
+            }
+          else
+            {
+              ref = g_new (GWeakRef, 1);
+              g_weak_ref_init (ref, NULL);
+              g_hash_table_insert (renderer->priv->cache_table,
+                                   hash, ref);
+            }
+
+          if (! buffer)
+            {
+              buffer = ANIMATION_GET_CLASS (animation)->create_frame (animation,
+                                                                      G_OBJECT (renderer),
+                                                                      i);
+              g_weak_ref_set (ref, buffer);
+            }
+        }
+
+      if (renderer->priv->cache[i])
+        g_object_unref (renderer->priv->cache[i]);
+      if (renderer->priv->hashes[i])
+        g_free (renderer->priv->hashes[i]);
+      renderer->priv->cache[i]  = buffer;
+      renderer->priv->hashes[i] = hash_cp;
+
+      g_signal_emit_by_name (renderer, "cache-updated", i);
+    }
+}
+
+static void
+on_duration_changed (Animation         *animation,
+                     gint               duration,
+                     AnimationRenderer *renderer)
+{
+  gint i;
+
+  if (duration < renderer->priv->cache_size)
+    {
+      for (i = duration; i < renderer->priv->cache_size; i++)
+        {
+          if (renderer->priv->cache[i])
+            g_object_unref (renderer->priv->cache[i]);
+          if (renderer->priv->hashes[i])
+            g_free (renderer->priv->hashes[i]);
+        }
+      renderer->priv->cache = g_renew (GeglBuffer*,
+                                       renderer->priv->cache,
+                                       duration);
+      renderer->priv->hashes = g_renew (gchar*,
+                                        renderer->priv->hashes,
+                                        duration);
+    }
+  else if (duration > renderer->priv->cache_size)
+    {
+      renderer->priv->cache = g_renew (GeglBuffer*,
+                                       renderer->priv->cache,
+                                       duration);
+      renderer->priv->hashes = g_renew (gchar*,
+                                        renderer->priv->hashes,
+                                        duration);
+      for (i = renderer->priv->cache_size; i < duration; i++)
+        {
+            renderer->priv->cache[i]  = NULL;
+            renderer->priv->hashes[i] = NULL;
+        }
+    }
+  renderer->priv->cache_size = duration;
+}
+
+/**** Public Functions ****/
+
+/**
+ * animation_renderer_new:
+ * @playback: the #AnimationPlayback.
+ *
+ * Returns: a new #AnimationRenderer. This renderer as well as all its methods
+ * should only be visible by the attached @playback.
+ **/
+GObject *
+animation_renderer_new (GObject *playback)
+{
+  GObject           *object;
+  AnimationRenderer *renderer;
+  Animation         *animation;
+
+  object = g_object_new (ANIMATION_TYPE_RENDERER,
+                         "playback", playback,
+                         NULL);
+  renderer = ANIMATION_RENDERER (object);
+
+  animation = animation_playback_get_animation (renderer->priv->playback);
+  renderer->priv->cache_size = animation_get_duration (animation);
+  renderer->priv->cache      = g_new0 (GeglBuffer*,
+                                       renderer->priv->cache_size);
+  renderer->priv->hashes     = g_new0 (gchar*,
+                                       renderer->priv->cache_size);
+  g_signal_connect (animation, "frames-changed",
+                    G_CALLBACK (on_frames_changed), renderer);
+  g_signal_connect (animation, "duration-changed",
+                    G_CALLBACK (on_duration_changed), renderer);
+
+  return object;
+}
+
+/**
+ * animation_renderer_get_buffer:
+ * @renderer: the #AnimationRenderer.
+ * @position:
+ *
+ * Returns: the #GeglBuffer cached for the frame at @position, with an
+ * additional reference, so that it will stay valid even if the frame is
+ * updated in-between. Therefore call g_object_unref() after usage.
+ * As all other renderer function, it should only be visible by the playback,
+ * or by frame-rendering code in #Animation subclasses themselves, since the
+ * renderer passes itself as argument when it requests a new frame buffer.
+ * Other pieces of code, in particular the GUI, should only call
+ * animation_playback_get_buffer().
+ **/
+GeglBuffer *
+animation_renderer_get_buffer (AnimationRenderer *renderer,
+                               gint               position)
+{
+  GeglBuffer *frame;
+
+  frame = renderer->priv->cache[position];
+  if (frame)
+    frame = g_object_ref (frame);
+
+  return frame;
+}
+
+gboolean
+animation_renderer_identical (AnimationRenderer *renderer,
+                              gint               position1,
+                              gint               position2)
+{
+  return (g_strcmp0 (renderer->priv->hashes[position1],
+                     renderer->priv->hashes[position2]) == 0);
+}
diff --git a/plug-ins/animation-play/core/animation-renderer.h 
b/plug-ins/animation-play/core/animation-renderer.h
new file mode 100644
index 0000000..8d374a7
--- /dev/null
+++ b/plug-ins/animation-play/core/animation-renderer.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation-renderer.h
+ * Copyright (C) 2017 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ANIMATION_RENDERER_H__
+#define __ANIMATION_RENDERER_H__
+
+#define ANIMATION_TYPE_RENDERER            (animation_renderer_get_type ())
+#define ANIMATION_RENDERER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_RENDERER, 
AnimationRenderer))
+#define ANIMATION_RENDERER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_RENDERER, 
AnimationRendererClass))
+#define ANIMATION_IS_RENDERER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_RENDERER))
+#define ANIMATION_IS_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_RENDERER))
+#define ANIMATION_RENDERER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_RENDERER, 
AnimationRendererClass))
+
+typedef struct _AnimationRenderer        AnimationRenderer;
+typedef struct _AnimationRendererClass   AnimationRendererClass;
+typedef struct _AnimationRendererPrivate AnimationRendererPrivate;
+
+struct _AnimationRenderer
+{
+  GObject                   parent_instance;
+
+  AnimationRendererPrivate *priv;
+};
+
+struct _AnimationRendererClass
+{
+  GObjectClass  parent_class;
+
+  void         (*cache_updated)  (AnimationRenderer *renderer,
+                                  gint               position);
+};
+
+GType         animation_renderer_get_type (void);
+
+
+GObject     * animation_renderer_new          (GObject           *playback);
+GeglBuffer  * animation_renderer_get_buffer   (AnimationRenderer *renderer,
+                                               gint               position);
+gboolean      animation_renderer_identical    (AnimationRenderer *renderer,
+                                               gint               position1,
+                                               gint               position2);
+
+#endif  /*  __ANIMATION_RENDERER_H__  */
diff --git a/plug-ins/animation-play/core/animation.c b/plug-ins/animation-play/core/animation.c
index 4b6bfc4..d4fc4c4 100644
--- a/plug-ins/animation-play/core/animation.c
+++ b/plug-ins/animation-play/core/animation.c
@@ -30,6 +30,8 @@
 #include "animation.h"
 #include "animation-animatic.h"
 #include "animation-celanimation.h"
+#include "animation-playback.h"
+#include "animation-renderer.h"
 
 /* Settings we cache assuming they may be the user's
  * favorite, like a framerate.
@@ -44,7 +46,7 @@ enum
 {
   LOADING,
   LOADED,
-  CACHE_INVALIDATED,
+  FRAMES_CHANGED,
   DURATION_CHANGED,
   FRAMERATE_CHANGED,
   PROXY_CHANGED,
@@ -88,11 +90,6 @@ static void       animation_get_property           (GObject      *object,
                                                     GValue       *value,
                                                     GParamSpec   *pspec);
 
-/* Base implementation of virtual methods. */
-static gboolean   animation_real_same              (Animation *animation,
-                                                    gint       prev_pos,
-                                                    gint       next_pos);
-
 G_DEFINE_TYPE (Animation, animation, G_TYPE_OBJECT)
 
 #define parent_class animation_parent_class
@@ -145,19 +142,19 @@ animation_class_init (AnimationClass *klass)
                   G_TYPE_NONE,
                   0);
   /**
-   * Animation::cache-invalidated:
+   * Animation::frames-changed:
    * @animation: the animation.
-   * @position: the first frame position whose cache is invalid.
-   * @length: the number of invalidated frames from @position.
+   * @position: the first frame position whose contents changed.
+   * @length: the number of changed frames from @position.
    *
-   * The ::cache-invalidated signal must be emitted when the contents
+   * The ::frames-changed signal must be emitted when the contents
    * of one or more successive frames change.
    */
-  animation_signals[CACHE_INVALIDATED] =
-    g_signal_new ("cache-invalidated",
+  animation_signals[FRAMES_CHANGED] =
+    g_signal_new ("frames-changed",
                   G_TYPE_FROM_CLASS (klass),
                   G_SIGNAL_RUN_FIRST,
-                  G_STRUCT_OFFSET (AnimationClass, cache_invalidated),
+                  G_STRUCT_OFFSET (AnimationClass, frames_changed),
                   NULL, NULL,
                   NULL,
                   G_TYPE_NONE,
@@ -222,8 +219,6 @@ animation_class_init (AnimationClass *klass)
   object_class->set_property = animation_set_property;
   object_class->get_property = animation_get_property;
 
-  klass->same                = animation_real_same;
-
   /**
    * Animation:image:
    *
@@ -279,6 +274,8 @@ animation_new (gint32       image_id,
                             "image", image_id,
                             "xml", xml,
                             NULL);
+  g_signal_emit (animation, animation_signals[FRAMES_CHANGED], 0,
+                 0, animation_get_duration (animation));
 
   return animation;
 }
@@ -304,10 +301,10 @@ animation_load (Animation *animation)
   AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
 
   priv->loaded = FALSE;
-  ANIMATION_GET_CLASS (animation)->purge_cache (animation);
-  priv->loaded = TRUE;
-  g_signal_emit (animation, animation_signals[CACHE_INVALIDATED], 0,
+  g_signal_emit (animation, animation_signals[FRAMES_CHANGED], 0,
                  0, animation_get_duration (animation));
+  priv->loaded = TRUE;
+  g_signal_emit (animation, animation_signals[LOADED], 0);
 }
 
 void
@@ -444,13 +441,6 @@ animation_get_size (Animation *animation,
   *height *= priv->proxy_ratio;
 }
 
-GeglBuffer *
-animation_get_frame (Animation *animation,
-                     gint       pos)
-{
-  return ANIMATION_GET_CLASS (animation)->get_frame (animation, pos);
-}
-
 void
 animation_set_framerate (Animation *animation,
                          gdouble    fps)
@@ -567,12 +557,3 @@ animation_get_property (GObject    *object,
       break;
     }
 }
-
-static gboolean
-animation_real_same (Animation *animation,
-                     gint       previous_pos,
-                     gint       next_pos)
-{
-  /* By default all frames are supposed different. */
-  return (previous_pos == next_pos);
-}
diff --git a/plug-ins/animation-play/core/animation.h b/plug-ins/animation-play/core/animation.h
index 03a01c4..3a17d7d 100644
--- a/plug-ins/animation-play/core/animation.h
+++ b/plug-ins/animation-play/core/animation.h
@@ -45,8 +45,9 @@ struct _AnimationClass
                                       gdouble       ratio);
   void         (*loaded)             (Animation    *animation);
 
-  void         (*cache_invalidated)  (Animation    *animation,
-                                      gint          position);
+  void         (*frames_changed)     (Animation    *animation,
+                                      gint          position,
+                                      gint          length);
   void         (*duration_changed)   (Animation    *animation,
                                       gint          duration);
   void         (*framerate_changed)  (Animation    *animation,
@@ -54,19 +55,9 @@ struct _AnimationClass
   void         (*proxy)              (Animation    *animation,
                                       gdouble       ratio);
 
-  /* Defaults to returning FALSE for any different position. */
-  gboolean     (*same)               (Animation    *animation,
-                                      gint          prev_pos,
-                                      gint          next_pos);
-
   /* These virtual methods must be implemented by any subclass. */
   gint         (*get_duration)       (Animation    *animation);
 
-  GeglBuffer * (*get_frame)          (Animation    *animation,
-                                      gint          pos);
-
-  void         (*purge_cache)        (Animation    *animation);
-
   void         (*reset_defaults)     (Animation    *animation);
   gchar      * (*serialize)          (Animation    *animation,
                                       const gchar  *playback_xml);
@@ -76,6 +67,13 @@ struct _AnimationClass
 
   void         (*update_paint_view)  (Animation    *animation,
                                       gint          position);
+
+  /* Used by the renderer only. Must be implemented too. */
+  gchar      * (*get_frame_hash)     (Animation    *animation,
+                                      gint          position);
+  GeglBuffer * (*create_frame)       (Animation    *animation,
+                                      GObject      *renderer,
+                                      gint          position);
 };
 
 GType         animation_get_type (void);
@@ -100,9 +98,6 @@ void          animation_get_size           (Animation   *animation,
                                             gint        *width,
                                             gint        *height);
 
-GeglBuffer  * animation_get_frame          (Animation   *animation,
-                                            gint         frame_number);
-
 void          animation_set_framerate      (Animation   *animation,
                                             gdouble      fps);
 gdouble       animation_get_framerate      (Animation   *animation);
diff --git a/plug-ins/animation-play/widgets/animation-dialog.c 
b/plug-ins/animation-play/widgets/animation-dialog.c
index 08eb1c6..2ef0493 100755
--- a/plug-ins/animation-play/widgets/animation-dialog.c
+++ b/plug-ins/animation-play/widgets/animation-dialog.c
@@ -1976,11 +1976,11 @@ detach_callback (GtkToggleAction *action,
     }
 
   /* Force a refresh after detachment/attachment. */
-  buffer = animation_get_frame (priv->animation,
-                                animation_playback_get_position (priv->playback));
+  buffer = animation_playback_get_buffer (priv->playback,
+                                          animation_playback_get_position (priv->playback));
   render_frame (dialog, buffer, TRUE);
   /* clean up */
-  if (buffer != NULL)
+  if (buffer)
     g_object_unref (buffer);
 }
 
@@ -2330,11 +2330,11 @@ da_size_callback (GtkWidget       *drawing_area,
             {
               GeglBuffer *buffer;
 
-              buffer = animation_get_frame (priv->animation,
-                                            animation_playback_get_position (priv->playback));
+              buffer = animation_playback_get_buffer (priv->playback,
+                                                      animation_playback_get_position (priv->playback));
               render_frame (dialog, buffer, TRUE);
               /* clean up */
-              if (buffer != NULL)
+              if (buffer)
                 g_object_unref (buffer);
             }
         }
@@ -2431,11 +2431,11 @@ render_on_realize (GtkWidget       *drawing_area,
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
   GeglBuffer *buffer;
 
-  buffer = animation_get_frame (priv->animation,
-                                animation_playback_get_position (priv->playback));
+  buffer = animation_playback_get_buffer (priv->playback,
+                                          animation_playback_get_position (priv->playback));
   render_frame (dialog, buffer, TRUE);
   /* clean up */
-  if (buffer != NULL)
+  if (buffer)
     g_object_unref (buffer);
 
   g_signal_handlers_disconnect_by_func (drawing_area,
diff --git a/plug-ins/animation-play/widgets/animation-xsheet.c 
b/plug-ins/animation-play/widgets/animation-xsheet.c
index dd9cf74..e1aae8a 100755
--- a/plug-ins/animation-play/widgets/animation-xsheet.c
+++ b/plug-ins/animation-play/widgets/animation-xsheet.c
@@ -1295,7 +1295,8 @@ on_playback_rendered (AnimationPlayback *playback,
                       gboolean           must_draw_null,
                       AnimationXSheet   *xsheet)
 {
-  animation_xsheet_jump (xsheet, frame_number);
+  if (animation_loaded (ANIMATION (xsheet->priv->animation)))
+    animation_xsheet_jump (xsheet, frame_number);
 }
 
 static void
@@ -1925,7 +1926,7 @@ animation_xsheet_rename_cel (AnimationXSheet *xsheet,
                              gboolean         recursively)
 {
   const GList *layers;
-  const GList *prev_layers;
+  const GList *prev_layers = NULL;
   gpointer     track_num;
   gpointer     position;
   gboolean     same_as_prev = FALSE;
@@ -1938,9 +1939,10 @@ animation_xsheet_rename_cel (AnimationXSheet *xsheet,
   layers = animation_cel_animation_get_layers (xsheet->priv->animation,
                                                GPOINTER_TO_INT (track_num),
                                                GPOINTER_TO_INT (position));
-  prev_layers = animation_cel_animation_get_layers (xsheet->priv->animation,
-                                                    GPOINTER_TO_INT (track_num),
-                                                    GPOINTER_TO_INT (position) - 1);
+  if (position > 0)
+    prev_layers = animation_cel_animation_get_layers (xsheet->priv->animation,
+                                                      GPOINTER_TO_INT (track_num),
+                                                      GPOINTER_TO_INT (position) - 1);
 
   /* Remove the previous label, if any. */
   if (gtk_bin_get_child (GTK_BIN (cel)))


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]