[gimp/wip/animation: 101/145] plug-ins: render the frame buffer in a separate thread.



commit 8a65ea6bc2b3f4e6e9dc918ebe19fba9e12a608f
Author: Jehan <jehan girinstud io>
Date:   Fri May 19 17:09:00 2017 +0200

    plug-ins: render the frame buffer in a separate thread.
    
    This way, the GUI does not have to hang forever each time the frames'
    composition changes. It still does for changes on many frames at once. I
    will have to investigate the issue later.
    All GTK+ code still has to stay in the same thread. This is why I emit
    the "cache-updated" signal in the main thread as regularly verified by a
    separate queue.
    Otherwise the plugin would crash with:
    > animation-play: Fatal IO error 11 (Resource temporarily unavailable) on X server :0.0.
    > [xcb] Unknown request in queue while dequeuing
    > [xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
    > [xcb] Aborting, sorry about that.
    > animation-play: xcb_io.c:165: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed.

 plug-ins/animation-play/core/animation-renderer.c |  218 +++++++++++++++++---
 plug-ins/animation-play/core/animation-renderer.h |   15 +-
 2 files changed, 193 insertions(+), 40 deletions(-)
---
diff --git a/plug-ins/animation-play/core/animation-renderer.c 
b/plug-ins/animation-play/core/animation-renderer.c
index 272439e..0417180 100644
--- a/plug-ins/animation-play/core/animation-renderer.c
+++ b/plug-ins/animation-play/core/animation-renderer.c
@@ -42,30 +42,47 @@ struct _AnimationRendererPrivate
 {
   AnimationPlayback  *playback;
 
+  GAsyncQueue        *queue;
+
+  GAsyncQueue        *ack_queue;
+  guint               idle_id;
+
   /* Frames are cached as GEGL buffers. */
+  GMutex              lock;
   GeglBuffer        **cache;
   gchar             **hashes;
   GHashTable         *cache_table;
   gint                cache_size;
+
+  GThread            *queue_thread;
 };
 
-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);
+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 gint     animation_renderer_sort_frame    (gconstpointer      f1,
+                                                  gconstpointer      f2,
+                                                  gpointer           data);
+
+static gpointer animation_renderer_process_queue (AnimationRenderer *renderer);
+static gboolean animation_renderer_idle_update   (AnimationRenderer *renderer);
+
+static void     on_frames_changed                (Animation         *animation,
+                                                  gint               position,
+                                                  gint               length,
+                                                  AnimationRenderer *renderer);
+static void     on_duration_changed              (Animation         *animation,
+                                                  gint               duration,
+                                                  AnimationRenderer *renderer);
+static void     on_animation_loaded              (Animation         *animation,
+                                                  AnimationRenderer *renderer);
 
 G_DEFINE_TYPE (AnimationRenderer, animation_renderer, G_TYPE_OBJECT)
 
@@ -122,9 +139,12 @@ animation_renderer_init (AnimationRenderer *renderer)
   renderer->priv = G_TYPE_INSTANCE_GET_PRIVATE (renderer,
                                                 ANIMATION_TYPE_RENDERER,
                                                 AnimationRendererPrivate);
+  g_mutex_init (&(renderer->priv->lock));
   renderer->priv->cache_table = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                        (GDestroyNotify) g_free,
                                                        (GDestroyNotify) g_weak_ref_clear);
+  renderer->priv->queue = g_async_queue_new_full ((GDestroyNotify) g_weak_ref_clear);
+  renderer->priv->ack_queue = g_async_queue_new_full ((GDestroyNotify) g_weak_ref_clear);
 }
 
 static void
@@ -136,6 +156,15 @@ animation_renderer_finalize (GObject *object)
 
   animation = animation_playback_get_animation (renderer->priv->playback);
 
+  /* Stop the thread. */
+  g_async_queue_push_front (renderer->priv->queue,
+                            GINT_TO_POINTER (- 1));
+  g_thread_join (renderer->priv->queue_thread);
+  g_thread_unref (renderer->priv->queue_thread);
+
+  /* Clean remaining data. */
+  g_source_remove (renderer->priv->idle_id);
+  g_mutex_lock (&renderer->priv->lock);
   for (i = 0; i < animation_get_duration (animation); i++)
     {
       if (renderer->priv->cache[i])
@@ -145,7 +174,12 @@ animation_renderer_finalize (GObject *object)
     }
   g_free (renderer->priv->cache);
   g_free (renderer->priv->hashes);
+
+  g_async_queue_unref (renderer->priv->queue);
+  g_async_queue_unref (renderer->priv->ack_queue);
   g_hash_table_destroy (renderer->priv->cache_table);
+  g_mutex_unlock (&renderer->priv->lock);
+  g_mutex_clear (&renderer->priv->lock);
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
@@ -190,22 +224,63 @@ animation_renderer_get_property (GObject      *object,
     }
 }
 
-static void
-on_frames_changed (Animation         *animation,
-                   gint               position,
-                   gint               length,
-                   AnimationRenderer *renderer)
+static gint
+animation_renderer_sort_frame (gconstpointer f1,
+                               gconstpointer f2,
+                               gpointer      data)
 {
-  gint i;
+  gint first_frame = GPOINTER_TO_INT (data);
+  gint frame1      = GPOINTER_TO_INT (f1);
+  gint frame2      = GPOINTER_TO_INT (f2);
+  gint invert;
 
-  for (i = position; i < position + length; i++)
+  if (frame1 == frame2)
+    {
+      return 0;
+    }
+  else
     {
+      invert = ((frame1 >= first_frame && frame2 >= first_frame) ||
+                (frame1 < first_frame && frame2 < first_frame)) ? 1 : -1;
+      if (frame1 < frame2)
+        return invert * -1;
+      else
+        return invert;
+    }
+}
+
+static gpointer
+animation_renderer_process_queue (AnimationRenderer *renderer)
+{
+  while (TRUE)
+    {
+      Animation  *animation;
       GeglBuffer *buffer  = NULL;
       GWeakRef   *ref     = NULL;
       gchar      *hash    = NULL;
       gchar      *hash_cp = NULL;
+      gint        frame;
+
+      frame = GPOINTER_TO_INT (g_async_queue_pop (renderer->priv->queue)) - 1;
 
-      hash = ANIMATION_GET_CLASS (animation)->get_frame_hash (animation, i);
+      /* End flag. */
+      if (frame < 0)
+        g_thread_exit (NULL);
+
+      /* It is possible to have position bigger than the animation duration if the
+       * request was sent before a duration change. When this happens, just ignore
+       * the request silently and go to the next one. */
+      g_mutex_lock (&renderer->priv->lock);
+      if (frame >= renderer->priv->cache_size)
+        {
+          g_mutex_unlock (&renderer->priv->lock);
+          continue;
+        }
+      g_mutex_unlock (&renderer->priv->lock);
+
+      animation = animation_playback_get_animation (renderer->priv->playback);
+      hash = ANIMATION_GET_CLASS (animation)->get_frame_hash (animation,
+                                                              frame);
       if (hash)
         {
           ref = g_hash_table_lookup (renderer->priv->cache_table, hash);
@@ -219,27 +294,83 @@ on_frames_changed (Animation         *animation,
             {
               ref = g_new (GWeakRef, 1);
               g_weak_ref_init (ref, NULL);
+              g_mutex_lock (&renderer->priv->lock);
               g_hash_table_insert (renderer->priv->cache_table,
                                    hash, ref);
+              g_mutex_unlock (&renderer->priv->lock);
             }
 
           if (! buffer)
             {
               buffer = ANIMATION_GET_CLASS (animation)->create_frame (animation,
                                                                       G_OBJECT (renderer),
-                                                                      i);
+                                                                      frame);
               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_mutex_lock (&renderer->priv->lock);
+      if (renderer->priv->cache[frame])
+        g_object_unref (renderer->priv->cache[frame]);
+      if (renderer->priv->hashes[frame])
+        g_free (renderer->priv->hashes[frame]);
+      renderer->priv->cache[frame]  = buffer;
+      renderer->priv->hashes[frame] = hash_cp;
+      g_mutex_unlock (&renderer->priv->lock);
+
+      /* Tell the main thread which buffers were updated, so that the
+       * GUI reflects the change. */
+      g_async_queue_remove (renderer->priv->ack_queue,
+                            GINT_TO_POINTER (frame + 1));
+      g_async_queue_push (renderer->priv->ack_queue,
+                          GINT_TO_POINTER (frame + 1));
+
+      /* Relinquish CPU regularly. */
+      g_thread_yield ();
+    }
+  return NULL;
+}
+
+static gboolean
+animation_renderer_idle_update (AnimationRenderer *renderer)
+{
+  gpointer p;
+
+  while ((p = g_async_queue_try_pop (renderer->priv->ack_queue)))
+    {
+      gint frame = GPOINTER_TO_INT (p) - 1;
+      g_signal_emit_by_name (renderer, "cache-updated", frame);
+    }
+  /* Make sure the UI gets updated regularly. */
+  while (g_main_context_pending (NULL))
+    g_main_context_iteration (NULL, FALSE);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+on_frames_changed (Animation         *animation,
+                   gint               position,
+                   gint               length,
+                   AnimationRenderer *renderer)
+{
+  gint i;
 
-      g_signal_emit_by_name (renderer, "cache-updated", i);
+  for (i = position; i < position + length; i++)
+    {
+      /* Remove if already present: don't process twice the same frames.
+       * XXX GAsyncQueue does not allow NULL data for no good reason (it relies
+       * on GQueue which does allow NULL data. As a trick, I just add 1 so that
+       * we can process the frame 0. */
+      g_async_queue_remove (renderer->priv->queue, GINT_TO_POINTER (i + 1));
+      g_async_queue_push_sorted (renderer->priv->queue,
+                                 GINT_TO_POINTER (i + 1),
+                                 (GCompareDataFunc) animation_renderer_sort_frame,
+                                 /* TODO: right now I am sorting the render
+                                  * queue in common order. I will have to test
+                                  * sorting it from the current position.
+                                  */
+                                 0);
     }
 }
 
@@ -250,6 +381,7 @@ on_duration_changed (Animation         *animation,
 {
   gint i;
 
+  g_mutex_lock (&renderer->priv->lock);
   if (duration < renderer->priv->cache_size)
     {
       for (i = duration; i < renderer->priv->cache_size; i++)
@@ -281,6 +413,22 @@ on_duration_changed (Animation         *animation,
         }
     }
   renderer->priv->cache_size = duration;
+  g_mutex_unlock (&renderer->priv->lock);
+}
+
+static void
+on_animation_loaded (Animation         *animation,
+                     AnimationRenderer *renderer)
+{
+  g_signal_handlers_disconnect_by_func (animation,
+                                        G_CALLBACK (on_animation_loaded),
+                                        renderer);
+  renderer->priv->queue_thread = g_thread_new ("gimp-animation-process-queue",
+                                               (GThreadFunc) animation_renderer_process_queue,
+                                               renderer);
+  renderer->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+                                             (GSourceFunc) animation_renderer_idle_update,
+                                             renderer, NULL);
 }
 
 /**** Public Functions ****/
@@ -314,6 +462,8 @@ animation_renderer_new (GObject *playback)
                     G_CALLBACK (on_frames_changed), renderer);
   g_signal_connect (animation, "duration-changed",
                     G_CALLBACK (on_duration_changed), renderer);
+  g_signal_connect (animation, "loaded",
+                    G_CALLBACK (on_animation_loaded), renderer);
 
   return object;
 }
@@ -338,9 +488,11 @@ animation_renderer_get_buffer (AnimationRenderer *renderer,
 {
   GeglBuffer *frame;
 
+  g_mutex_lock (&renderer->priv->lock);
   frame = renderer->priv->cache[position];
   if (frame)
     frame = g_object_ref (frame);
+  g_mutex_unlock (&renderer->priv->lock);
 
   return frame;
 }
diff --git a/plug-ins/animation-play/core/animation-renderer.h 
b/plug-ins/animation-play/core/animation-renderer.h
index 8d374a7..0de5115 100644
--- a/plug-ins/animation-play/core/animation-renderer.h
+++ b/plug-ins/animation-play/core/animation-renderer.h
@@ -47,14 +47,15 @@ struct _AnimationRendererClass
                                   gint               position);
 };
 
-GType         animation_renderer_get_type (void);
+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);
+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__  */


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