/* === 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 #endif #include "asyncrenderer.h" #include "app.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SIGNAL_H #include #endif #include #include #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 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_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 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_alpha_mode(warm_target->get_alpha_mode()); 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); Glib::TimeVal end_time; end_time.assign_current_time(); end_time.add_microseconds(BOREDOM_TIMEOUT); if(!tile_queue.empty() && alive_flag) { if(cond_tile_queue_empty.timed_wait(mutex,end_time)) break; } else break; } #ifdef SINGLE_THREADED } #endif Glib::Mutex::Lock lock(mutex); if(!alive_flag) return; return warm_target->end_frame(); } }; class AsyncTarget_Cairo_Tile : public synfig::Target_Cairo_Tile { public: etl::handle warm_target; class tile_t { public: cairo_surface_t* surface; int x,y; tile_t(): surface(NULL), x(0), y(0) { } tile_t(cairo_surface_t*& surface_,int x_, int y_) { if(surface_) surface=cairo_surface_reference(surface_); else surface=surface_; x=x_; y=y_; } tile_t(const tile_t &other): surface(cairo_surface_reference(other.surface)), x(other.x), y(other.y) { } ~tile_t() { if(surface) cairo_surface_destroy(surface); } }; std::list 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_Cairo_Tile(etl::handle 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_alpha_mode(warm_target->get_alpha_mode()); 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_Cairo_Tile::tile_ready)); #endif } ~AsyncTarget_Cairo_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(cairo_surface_t* surface, int gx, int gy) { if(cairo_surface_status(surface)) return false; 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_Cairo_Tile::tile_ready), false ) ,0 ); #else tile_ready_signal(); #endif } //cairo_surface_destroy(surface); 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); Glib::TimeVal end_time; end_time.assign_current_time(); end_time.add_microseconds(BOREDOM_TIMEOUT); if(!tile_queue.empty() && alive_flag) { if(cond_tile_queue_empty.timed_wait(mutex,end_time)) 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 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 warm_target): warm_target(warm_target), scanline_(), alive_flag(), ready_next() { set_avoid_time_sync(warm_target->get_avoid_time_sync()); set_canvas(warm_target->get_canvas()); set_quality(warm_target->get_quality()); set_alpha_mode(warm_target->get_alpha_mode()); 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 { Glib::TimeVal end_time; end_time.assign_current_time(); end_time.add_microseconds(BOREDOM_TIMEOUT); while(alive_flag && !ready_next) { Glib::Mutex::Lock lock(mutex); if(cond_frame_queue_empty.timed_wait(mutex, end_time)) 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; } }; class AsyncTarget_Cairo : public synfig::Target_Cairo { public: etl::handle warm_target; cairo_surface_t* surface; ProgressCallback *callback; 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_Cairo(etl::handle warm_target): warm_target(warm_target), surface(), callback(), alive_flag(), ready_next() { set_avoid_time_sync(warm_target->get_avoid_time_sync()); set_canvas(warm_target->get_canvas()); set_quality(warm_target->get_quality()); set_alpha_mode(warm_target->get_alpha_mode()); 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_Cairo::frame_ready)); #endif } ~AsyncTarget_Cairo() { ready_connection.disconnect(); } virtual int next_frame(Time& time) { Glib::Mutex::Lock lock(mutex); if(!alive_flag) return 0; return warm_target->next_frame(time); } void set_dead() { Glib::Mutex::Lock lock(mutex); alive_flag=false; } virtual bool put_surface(cairo_surface_t* s, ProgressCallback *cb) { { Glib::Mutex::Lock lock(mutex); surface=s; callback=cb; if(!alive_flag) return false; ready_next=false; #ifdef GLIB_DISPATCHER_BROKEN ready_connection=Glib::signal_timeout().connect( sigc::bind_return(sigc::mem_fun(*this,&AsyncTarget_Cairo::frame_ready),false) ,0 ); #else frame_ready_signal(); #endif } #ifdef SINGLE_THREADED if (single_threaded()) signal_progress()(); else #endif { Glib::TimeVal end_time; end_time.assign_current_time(); end_time.add_microseconds(BOREDOM_TIMEOUT); while(alive_flag && !ready_next) { Glib::Mutex::Lock lock(mutex); if(cond_frame_queue_empty.timed_wait(mutex, end_time)) break; } } return true; } void frame_ready() { Glib::Mutex::Lock lock(mutex); if(alive_flag) alive_flag=warm_target->put_surface(surface, callback); #ifdef SINGLE_THREADED if (!single_threaded()) #endif cond_frame_queue_empty.signal(); ready_next=true; } virtual bool obtain_surface(cairo_surface_t*& s) { Glib::Mutex::Lock lock(mutex); if(!alive_flag) return false; return warm_target->obtain_surface(s); } }; /* === 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 target_,synfig::ProgressCallback *cb): error(false), success(false), cb(cb), #ifdef SINGLE_THREADED updating(false), #endif start_time(0, 0), finish_time(0, 0) { render_thread=0; if(etl::handle::cast_dynamic(target_)) { etl::handle wrap_target( new AsyncTarget_Tile(etl::handle::cast_dynamic(target_)) ); signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Tile::set_dead)); target=wrap_target; } else if(etl::handle::cast_dynamic(target_)) { etl::handle wrap_target( new AsyncTarget_Scanline( etl::handle::cast_dynamic(target_) ) ); signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Scanline::set_dead)); target=wrap_target; } else if(etl::handle::cast_dynamic(target_)) { etl::handle wrap_target( new AsyncTarget_Cairo_Tile( etl::handle::cast_dynamic(target_) ) ); signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Cairo_Tile::set_dead)); target=wrap_target; } else if(etl::handle::cast_dynamic(target_)) { etl::handle wrap_target( new AsyncTarget_Cairo( etl::handle::cast_dynamic(target_) ) ); signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Cairo::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 finish_time.assign_current_time(); // Make sure all the dispatch crap is cleared out //Glib::MainContext::get_default()->iteration(false); if(success) signal_success_(); target=0; render_thread=0; lock.release(); signal_finished_(); } } } void AsyncRenderer::pause() { } void AsyncRenderer::resume() { } void AsyncRenderer::start() { start_time.assign_current_time(); finish_time = start_time; done_connection=Glib::signal_timeout().connect( sigc::bind_return( mem_fun(*this,&AsyncRenderer::start_), false ) , 0 ); } #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(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(); } }