Remove ancient trunk folder from svn repository
[synfig.git] / synfig-studio / src / gtkmm / asyncrenderer.cpp
diff --git a/synfig-studio/src/gtkmm/asyncrenderer.cpp b/synfig-studio/src/gtkmm/asyncrenderer.cpp
new file mode 100644 (file)
index 0000000..72a286c
--- /dev/null
@@ -0,0 +1,541 @@
+/* === S Y N F I G ========================================================= */
+/*!    \file asyncrenderer.cpp
+**     \brief Template File
+**
+**     $Id$
+**
+**     \legal
+**     Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
+**     Copyright (c) 2007 Chris Moore
+**
+**     This package 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 2 of
+**     the License, or (at your option) any later version.
+**
+**     This package 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.
+**     \endlegal
+*/
+/* ========================================================================= */
+
+/* === H E A D E R S ======================================================= */
+
+#ifdef USING_PCH
+#      include "pch.h"
+#else
+#ifdef HAVE_CONFIG_H
+#      include <config.h>
+#endif
+
+#include "asyncrenderer.h"
+#include "app.h"
+#include <glibmm/thread.h>
+#include <glibmm/dispatcher.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+
+#include <synfig/general.h>
+#include <ETL/clock>
+
+#include "general.h"
+
+#endif
+
+/* === U S I N G =========================================================== */
+
+using namespace std;
+using namespace etl;
+using namespace synfig;
+using namespace studio;
+
+#define BOREDOM_TIMEOUT                50
+
+#define REJOIN_ON_STOP 1
+
+// The Glib::Dispatcher class is broken as of Glibmm 2.4.5.
+// Defining this macro enables the workaround.
+#define GLIB_DISPATCHER_BROKEN 1
+
+/* === C L A S S E S ======================================================= */
+
+class AsyncTarget_Tile : public synfig::Target_Tile
+{
+public:
+       etl::handle<synfig::Target_Tile> warm_target;
+
+       struct tile_t
+       {
+               Surface surface;
+               int x,y;
+               tile_t(const Surface& surface,int x, int y):
+                       surface(surface),
+                       x(x),y(y)
+               {
+               }
+       };
+       std::list<tile_t> tile_queue;
+       Glib::Mutex mutex;
+
+#ifndef GLIB_DISPATCHER_BROKEN
+       Glib::Dispatcher tile_ready_signal;
+#endif
+       Glib::Cond cond_tile_queue_empty;
+       bool alive_flag;
+
+       sigc::connection ready_connection;
+
+public:
+       AsyncTarget_Tile(etl::handle<synfig::Target_Tile> warm_target):
+               warm_target(warm_target)
+       {
+               set_avoid_time_sync(warm_target->get_avoid_time_sync());
+               set_tile_w(warm_target->get_tile_w());
+               set_tile_h(warm_target->get_tile_h());
+               set_canvas(warm_target->get_canvas());
+               set_quality(warm_target->get_quality());
+               set_remove_alpha(warm_target->get_remove_alpha());
+               set_threads(warm_target->get_threads());
+               set_clipping(warm_target->get_clipping());
+               set_rend_desc(&warm_target->rend_desc());
+               alive_flag=true;
+#ifndef GLIB_DISPATCHER_BROKEN
+               ready_connection=tile_ready_signal.connect(sigc::mem_fun(*this,&AsyncTarget_Tile::tile_ready));
+#endif
+       }
+
+       ~AsyncTarget_Tile()
+       {
+               ready_connection.disconnect();
+       }
+       void set_dead()
+       {
+               Glib::Mutex::Lock lock(mutex);
+               alive_flag=false;
+       }
+
+       virtual int total_tiles()const
+       {
+               return warm_target->total_tiles();
+       }
+
+       virtual int next_tile(int& x, int& y)
+       {
+               if(!alive_flag)
+                       return 0;
+
+               return warm_target->next_tile(x,y);
+       }
+
+       virtual int next_frame(Time& time)
+       {
+               if(!alive_flag)
+                       return 0;
+               return warm_target->next_frame(time);
+       }
+
+       virtual bool start_frame(synfig::ProgressCallback *cb=0)
+       {
+               if(!alive_flag)
+                       return false;
+               return warm_target->start_frame(cb);
+       }
+
+       virtual bool add_tile(const synfig::Surface &surface, int gx, int gy)
+       {
+               assert(surface);
+               if(!alive_flag)
+                       return false;
+               Glib::Mutex::Lock lock(mutex);
+               tile_queue.push_back(tile_t(surface,gx,gy));
+               if(tile_queue.size()==1)
+               {
+#ifdef GLIB_DISPATCHER_BROKEN
+               ready_connection=Glib::signal_timeout().connect(
+                       sigc::bind_return(
+                               sigc::mem_fun(*this,&AsyncTarget_Tile::tile_ready),
+                               false
+                       )
+                       ,0
+               );
+#else
+               tile_ready_signal();
+#endif
+               }
+
+               return alive_flag;
+       }
+
+       void tile_ready()
+       {
+               Glib::Mutex::Lock lock(mutex);
+               if(!alive_flag)
+               {
+                       tile_queue.clear();
+                       cond_tile_queue_empty.signal();
+                       return;
+               }
+               while(!tile_queue.empty() && alive_flag)
+               {
+                       tile_t& tile(tile_queue.front());
+
+                       if (getenv("SYNFIG_SHOW_TILE_OUTLINES"))
+                       {
+                               Color red(1,0,0);
+                               tile.surface.fill(red, 0, 0, 1, tile.surface.get_h());
+                               tile.surface.fill(red, 0, 0, tile.surface.get_w(), 1);
+                       }
+
+                       alive_flag=warm_target->add_tile(tile.surface,tile.x,tile.y);
+
+                       tile_queue.pop_front();
+               }
+               cond_tile_queue_empty.signal();
+       }
+
+       virtual void end_frame()
+       {
+#ifdef SINGLE_THREADED
+               if (!single_threaded())
+               {
+#endif
+                       while(alive_flag)
+                       {
+                               Glib::Mutex::Lock lock(mutex);
+                               if(!tile_queue.empty() && alive_flag)
+                               {
+                                       if(cond_tile_queue_empty.timed_wait(mutex,Glib::TimeVal(0,BOREDOM_TIMEOUT)))
+                                               break;
+                               }
+                               else
+                                       break;
+                       }
+#ifdef SINGLE_THREADED
+               }
+#endif
+               Glib::Mutex::Lock lock(mutex);
+               if(!alive_flag)
+                       return;
+               return warm_target->end_frame();
+       }
+};
+
+
+
+class AsyncTarget_Scanline : public synfig::Target_Scanline
+{
+public:
+       etl::handle<synfig::Target_Scanline> warm_target;
+
+       int scanline_;
+       Surface surface;
+
+       Glib::Mutex mutex;
+
+#ifndef GLIB_DISPATCHER_BROKEN
+       Glib::Dispatcher frame_ready_signal;
+#endif
+       Glib::Cond cond_frame_queue_empty;
+       bool alive_flag;
+       bool ready_next;
+       sigc::connection ready_connection;
+
+
+public:
+       AsyncTarget_Scanline(etl::handle<synfig::Target_Scanline> warm_target):
+               warm_target(warm_target)
+       {
+               set_avoid_time_sync(warm_target->get_avoid_time_sync());
+               set_canvas(warm_target->get_canvas());
+               set_quality(warm_target->get_quality());
+               set_remove_alpha(warm_target->get_remove_alpha());
+               set_threads(warm_target->get_threads());
+               set_rend_desc(&warm_target->rend_desc());
+               alive_flag=true;
+#ifndef GLIB_DISPATCHER_BROKEN
+               ready_connection=frame_ready_signal.connect(sigc::mem_fun(*this,&AsyncTarget_Scanline::frame_ready));
+#endif
+               surface.set_wh(warm_target->rend_desc().get_w(),warm_target->rend_desc().get_h());
+       }
+
+       ~AsyncTarget_Scanline()
+       {
+               ready_connection.disconnect();
+       }
+
+       virtual int next_frame(Time& time)
+       {
+               if(!alive_flag)
+                       return 0;
+               return warm_target->next_frame(time);
+
+       }
+
+       void set_dead()
+       {
+               Glib::Mutex::Lock lock(mutex);
+               alive_flag=false;
+       }
+
+       virtual bool start_frame(synfig::ProgressCallback */*cb*/=0)
+       {
+               return alive_flag;
+       }
+
+       virtual void end_frame()
+       {
+               {
+                       Glib::Mutex::Lock lock(mutex);
+
+                       if(!alive_flag)
+                               return;
+                       ready_next=false;
+
+#ifdef GLIB_DISPATCHER_BROKEN
+               ready_connection=Glib::signal_timeout().connect(
+                       sigc::bind_return(
+                               sigc::mem_fun(*this,&AsyncTarget_Scanline::frame_ready),
+                               false
+                       )
+                       ,0
+               );
+#else
+                       frame_ready_signal();
+#endif
+               }
+
+#ifdef SINGLE_THREADED
+               if (single_threaded())
+                       signal_progress()();
+               else
+#endif
+                       while(alive_flag && !ready_next)
+                       {
+                               Glib::Mutex::Lock lock(mutex);
+                               if(cond_frame_queue_empty.timed_wait(mutex,Glib::TimeVal(0,BOREDOM_TIMEOUT)))
+                                       break;
+                       }
+       }
+
+
+       virtual Color * start_scanline(int scanline)
+       {
+               Glib::Mutex::Lock lock(mutex);
+
+               return surface[scanline];
+       }
+
+       virtual bool end_scanline()
+       {
+               return alive_flag;
+       }
+
+       void frame_ready()
+       {
+               Glib::Mutex::Lock lock(mutex);
+               if(alive_flag)
+                       alive_flag=warm_target->add_frame(&surface);
+#ifdef SINGLE_THREADED
+               if (!single_threaded())
+#endif
+                       cond_frame_queue_empty.signal();
+               ready_next=true;
+       }
+};
+
+/* === G L O B A L S ======================================================= */
+
+/* === P R O C E D U R E S ================================================= */
+
+/* === M E T H O D S ======================================================= */
+
+AsyncRenderer::AsyncRenderer(etl::handle<synfig::Target> target_,synfig::ProgressCallback *cb):
+       error(false),
+       success(false),
+       cb(cb)
+#ifdef SINGLE_THREADED
+       , updating(false)
+#endif
+{
+       render_thread=0;
+       if(etl::handle<synfig::Target_Tile>::cast_dynamic(target_))
+       {
+               etl::handle<AsyncTarget_Tile> wrap_target(
+                       new AsyncTarget_Tile(etl::handle<synfig::Target_Tile>::cast_dynamic(target_))
+               );
+
+               signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Tile::set_dead));
+
+               target=wrap_target;
+       }
+       else if(etl::handle<synfig::Target_Scanline>::cast_dynamic(target_))
+       {
+               etl::handle<AsyncTarget_Scanline> wrap_target(
+                       new AsyncTarget_Scanline(
+                               etl::handle<synfig::Target_Scanline>::cast_dynamic(target_)
+                       )
+               );
+
+               signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Scanline::set_dead));
+
+               target=wrap_target;
+       }
+}
+
+AsyncRenderer::~AsyncRenderer()
+{
+       stop();
+}
+
+void
+AsyncRenderer::stop()
+{
+       if(target)
+       {
+               Glib::Mutex::Lock lock(mutex);
+               done_connection.disconnect();
+
+               if(render_thread)
+               {
+                       signal_stop_();
+
+#if REJOIN_ON_STOP
+#ifdef SINGLE_THREADED
+                       if (!single_threaded())
+#endif
+                               render_thread->join();
+#endif
+
+                       // Make sure all the dispatch crap is cleared out
+                       //Glib::MainContext::get_default()->iteration(false);
+
+                       if(success)
+                               signal_success_();
+
+                       signal_finished_();
+
+                       target=0;
+                       render_thread=0;
+               }
+       }
+}
+
+void
+AsyncRenderer::pause()
+{
+}
+
+void
+AsyncRenderer::resume()
+{
+}
+
+void
+AsyncRenderer::start()
+{
+       done_connection=Glib::signal_timeout().connect(
+               sigc::bind_return(
+                       mem_fun(*this,&AsyncRenderer::start_),
+                       false
+               )
+               ,50
+       );
+}
+
+#ifdef SINGLE_THREADED
+void
+AsyncRenderer::rendering_progress()
+{
+       updating = true;
+       while(studio::App::events_pending()) studio::App::iteration(false);
+       updating = false;
+}
+#endif
+
+void
+AsyncRenderer::start_()
+{
+       error=false;success=false;
+       if(target)
+       {
+#ifndef GLIB_DISPATCHER_BROKEN
+               done_connection=signal_done_.connect(mem_fun(*this,&AsyncRenderer::stop));
+#endif
+
+#ifdef SINGLE_THREADED
+               if (single_threaded())
+               {
+                       synfig::info("%s:%d rendering in the same thread", __FILE__, __LINE__);
+                       target->signal_progress().connect(sigc::mem_fun(this,&AsyncRenderer::rendering_progress));
+                       render_thread = (Glib::Thread*)1;
+                       render_target();
+               }
+               else
+#endif
+               {
+                       render_thread=Glib::Thread::create(
+                               sigc::mem_fun(*this,&AsyncRenderer::render_target),
+#if REJOIN_ON_STOP
+                               true
+#else
+                               false
+#endif
+                               );
+                       assert(render_thread);
+               }
+       }
+       else
+       {
+               stop();
+       }
+}
+
+void
+AsyncRenderer::render_target()
+{
+       etl::handle<Target> target(AsyncRenderer::target);
+
+       if(target && target->render())
+       {
+               success=true;
+       }
+       else
+       {
+               error=true;
+#ifndef REJOIN_ON_STOP
+               return;
+#endif
+       }
+
+       if(mutex.trylock())
+       {
+#ifdef GLIB_DISPATCHER_BROKEN
+               done_connection=Glib::signal_timeout().connect(
+                       sigc::bind_return(
+                               mem_fun(*this,&AsyncRenderer::stop),
+                               false
+                       )
+                       ,0
+               );
+#else
+               signal_done_.emit();
+#endif
+               mutex.unlock();
+       }
+}