Implements PXEGeek's http://wiki.synfig.com/Wish_list entry: "Optionally display...
[synfig.git] / synfig-studio / trunk / src / gtkmm / asyncrenderer.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file asyncrenderer.cpp
3 **      \brief Template File
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **
10 **      This package is free software; you can redistribute it and/or
11 **      modify it under the terms of the GNU General Public License as
12 **      published by the Free Software Foundation; either version 2 of
13 **      the License, or (at your option) any later version.
14 **
15 **      This package is distributed in the hope that it will be useful,
16 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
17 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 **      General Public License for more details.
19 **      \endlegal
20 */
21 /* ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 #       include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #       include <config.h>
30 #endif
31
32 #include "asyncrenderer.h"
33 #include <glibmm/thread.h>
34 #include <glibmm/dispatcher.h>
35
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39
40 #ifdef HAVE_SYS_TYPES_H
41 #include <sys/types.h>
42 #endif
43
44 #ifdef HAVE_SYS_WAIT_H
45 #include <sys/wait.h>
46 #endif
47
48 #ifdef HAVE_SIGNAL_H
49 #include <signal.h>
50 #endif
51
52 #include <synfig/general.h>
53 #include <ETL/clock>
54
55 #endif
56
57 /* === U S I N G =========================================================== */
58
59 using namespace std;
60 using namespace etl;
61 using namespace synfig;
62 using namespace studio;
63
64 #define BOREDOM_TIMEOUT         50
65
66 #define REJOIN_ON_STOP  1
67
68 // The Glib::Dispatcher class is broken as of Glibmm 2.4.5.
69 // Defining this macro enables the workaround.
70 #define GLIB_DISPATCHER_BROKEN 1
71
72 /* === C L A S S E S ======================================================= */
73
74 class AsyncTarget_Tile : public synfig::Target_Tile
75 {
76 public:
77         etl::handle<synfig::Target_Tile> warm_target;
78
79         struct tile_t
80         {
81                 Surface surface;
82                 int x,y;
83                 tile_t(const Surface& surface,int x, int y):
84                         surface(surface),
85                         x(x),y(y)
86                 {
87                 }
88         };
89         std::list<tile_t> tile_queue;
90         Glib::Mutex mutex;
91
92 #ifndef GLIB_DISPATCHER_BROKEN
93         Glib::Dispatcher tile_ready_signal;
94 #endif
95         Glib::Cond cond_tile_queue_empty;
96         bool alive_flag;
97
98         sigc::connection ready_connection;
99
100 public:
101         AsyncTarget_Tile(etl::handle<synfig::Target_Tile> warm_target):
102                 warm_target(warm_target)
103         {
104                 set_avoid_time_sync(warm_target->get_avoid_time_sync());
105                 set_tile_w(warm_target->get_tile_w());
106                 set_tile_h(warm_target->get_tile_h());
107                 set_canvas(warm_target->get_canvas());
108                 set_quality(warm_target->get_quality());
109                 set_remove_alpha(warm_target->get_remove_alpha());
110                 set_threads(warm_target->get_threads());
111                 set_clipping(warm_target->get_clipping());
112                 set_rend_desc(&warm_target->rend_desc());
113                 alive_flag=true;
114 #ifndef GLIB_DISPATCHER_BROKEN
115                 ready_connection=tile_ready_signal.connect(sigc::mem_fun(*this,&AsyncTarget_Tile::tile_ready));
116 #endif
117         }
118
119         ~AsyncTarget_Tile()
120         {
121                 ready_connection.disconnect();
122         }
123         void set_dead()
124         {
125                 Glib::Mutex::Lock lock(mutex);
126                 alive_flag=false;
127         }
128
129         virtual int total_tiles()const
130         {
131                 return warm_target->total_tiles();
132         }
133
134         virtual int next_tile(int& x, int& y)
135         {
136                 if(!alive_flag)
137                         return 0;
138
139                 return warm_target->next_tile(x,y);
140         }
141
142         virtual int next_frame(Time& time)
143         {
144                 if(!alive_flag)
145                         return 0;
146                 return warm_target->next_frame(time);
147         }
148
149         virtual bool start_frame(synfig::ProgressCallback *cb=0)
150         {
151                 if(!alive_flag)
152                         return false;
153                 return warm_target->start_frame(cb);
154         }
155
156         virtual bool add_tile(const synfig::Surface &surface, int gx, int gy)
157         {
158                 assert(surface);
159                 if(!alive_flag)
160                         return false;
161                 Glib::Mutex::Lock lock(mutex);
162                 tile_queue.push_back(tile_t(surface,gx,gy));
163                 if(tile_queue.size()==1)
164                 {
165 #ifdef GLIB_DISPATCHER_BROKEN
166                 ready_connection=Glib::signal_timeout().connect(
167                         sigc::bind_return(
168                                 sigc::mem_fun(*this,&AsyncTarget_Tile::tile_ready),
169                                 false
170                         )
171                         ,0
172                 );
173 #else
174                 tile_ready_signal();
175 #endif
176                 }
177
178                 return alive_flag;
179         }
180
181         void tile_ready()
182         {
183                 Glib::Mutex::Lock lock(mutex);
184                 if(!alive_flag)
185                 {
186                         tile_queue.clear();
187                         cond_tile_queue_empty.signal();
188                         return;
189                 }
190                 while(!tile_queue.empty() && alive_flag)
191                 {
192                         tile_t& tile(tile_queue.front());
193
194                         alive_flag=warm_target->add_tile(tile.surface,tile.x,tile.y);
195
196                         tile_queue.pop_front();
197                 }
198                 cond_tile_queue_empty.signal();
199         }
200
201         virtual void end_frame()
202         {
203                 while(alive_flag)
204                 {
205                         Glib::Mutex::Lock lock(mutex);
206                         if(!tile_queue.empty() && alive_flag)
207                         {
208                                 if(cond_tile_queue_empty.timed_wait(mutex,Glib::TimeVal(0,BOREDOM_TIMEOUT)))
209                                         break;
210                         }
211                         else
212                                 break;
213                 }
214                 Glib::Mutex::Lock lock(mutex);
215                 if(!alive_flag)
216                         return;
217                 return warm_target->end_frame();
218         }
219 };
220
221
222
223 class AsyncTarget_Scanline : public synfig::Target_Scanline
224 {
225 public:
226         etl::handle<synfig::Target_Scanline> warm_target;
227
228         int scanline_;
229         Surface surface;
230
231         Glib::Mutex mutex;
232
233 #ifndef GLIB_DISPATCHER_BROKEN
234         Glib::Dispatcher frame_ready_signal;
235 #endif
236         Glib::Cond cond_frame_queue_empty;
237         bool alive_flag;
238         bool ready_next;
239         sigc::connection ready_connection;
240
241
242 public:
243         AsyncTarget_Scanline(etl::handle<synfig::Target_Scanline> warm_target):
244                 warm_target(warm_target)
245         {
246                 set_avoid_time_sync(warm_target->get_avoid_time_sync());
247                 set_canvas(warm_target->get_canvas());
248                 set_quality(warm_target->get_quality());
249                 set_remove_alpha(warm_target->get_remove_alpha());
250                 set_threads(warm_target->get_threads());
251                 set_rend_desc(&warm_target->rend_desc());
252                 alive_flag=true;
253 #ifndef GLIB_DISPATCHER_BROKEN
254                 ready_connection=frame_ready_signal.connect(sigc::mem_fun(*this,&AsyncTarget_Scanline::frame_ready));
255 #endif
256                 surface.set_wh(warm_target->rend_desc().get_w(),warm_target->rend_desc().get_h());
257         }
258
259         ~AsyncTarget_Scanline()
260         {
261                 ready_connection.disconnect();
262         }
263
264         virtual int next_frame(Time& time)
265         {
266                 if(!alive_flag)
267                         return 0;
268                 return warm_target->next_frame(time);
269
270         }
271
272         void set_dead()
273         {
274                 Glib::Mutex::Lock lock(mutex);
275                 alive_flag=false;
276         }
277
278         virtual bool start_frame(synfig::ProgressCallback *cb=0)
279         {
280                 return alive_flag;
281         }
282
283         virtual void end_frame()
284         {
285                 {
286                         Glib::Mutex::Lock lock(mutex);
287
288                         if(!alive_flag)
289                                 return;
290                         ready_next=false;
291
292 #ifdef GLIB_DISPATCHER_BROKEN
293                 ready_connection=Glib::signal_timeout().connect(
294                         sigc::bind_return(
295                                 sigc::mem_fun(*this,&AsyncTarget_Scanline::frame_ready),
296                                 false
297                         )
298                         ,0
299                 );
300 #else
301                         frame_ready_signal();
302 #endif
303                         }
304
305                 while(alive_flag && !ready_next)
306                 {
307                         Glib::Mutex::Lock lock(mutex);
308                         if(cond_frame_queue_empty.timed_wait(mutex,Glib::TimeVal(0,BOREDOM_TIMEOUT)))
309                                 break;
310                 }
311         }
312
313
314         virtual Color * start_scanline(int scanline)
315         {
316                 Glib::Mutex::Lock lock(mutex);
317
318                 return surface[scanline];
319         }
320
321         virtual bool end_scanline()
322         {
323                 return alive_flag;
324         }
325
326         void frame_ready()
327         {
328                 Glib::Mutex::Lock lock(mutex);
329                 if(alive_flag)
330                         alive_flag=warm_target->add_frame(&surface);
331                 cond_frame_queue_empty.signal();
332                 ready_next=true;
333         }
334 };
335
336 /* === G L O B A L S ======================================================= */
337
338 /* === P R O C E D U R E S ================================================= */
339
340 /* === M E T H O D S ======================================================= */
341
342 AsyncRenderer::AsyncRenderer(etl::handle<synfig::Target> target_,synfig::ProgressCallback *cb):
343         error(false),
344         success(false),
345         cb(cb)
346 {
347         render_thread=0;
348         if(etl::handle<synfig::Target_Tile>::cast_dynamic(target_))
349         {
350                 etl::handle<AsyncTarget_Tile> wrap_target(
351                         new AsyncTarget_Tile(etl::handle<synfig::Target_Tile>::cast_dynamic(target_))
352                 );
353
354                 signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Tile::set_dead));
355
356                 target=wrap_target;
357         }
358         else if(etl::handle<synfig::Target_Scanline>::cast_dynamic(target_))
359         {
360                 etl::handle<AsyncTarget_Scanline> wrap_target(
361                         new AsyncTarget_Scanline(
362                                 etl::handle<synfig::Target_Scanline>::cast_dynamic(target_)
363                         )
364                 );
365
366                 signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Scanline::set_dead));
367
368                 target=wrap_target;
369         }
370 }
371
372 AsyncRenderer::~AsyncRenderer()
373 {
374         stop();
375 }
376
377 void
378 AsyncRenderer::stop()
379 {
380         if(target)
381         {
382                 Glib::Mutex::Lock lock(mutex);
383                 done_connection.disconnect();
384
385                 if(render_thread)
386                 {
387                         signal_stop_();
388
389 #if REJOIN_ON_STOP
390                         render_thread->join();
391 #endif
392
393                         // Make sure all the dispatch crap is cleared out
394                         //Glib::MainContext::get_default()->iteration(false);
395
396                         if(success)
397                                 signal_success_();
398
399                         signal_finished_();
400
401                         target=0;
402                         render_thread=0;
403                 }
404         }
405 }
406
407 void
408 AsyncRenderer::pause()
409 {
410 }
411
412 void
413 AsyncRenderer::resume()
414 {
415 }
416
417 void
418 AsyncRenderer::start()
419 {
420         done_connection=Glib::signal_timeout().connect(
421                 sigc::bind_return(
422                         mem_fun(*this,&AsyncRenderer::start_),
423                         false
424                 )
425                 ,50
426         );
427 }
428
429 void
430 AsyncRenderer::start_()
431 {
432         error=false;success=false;
433         if(target)
434         {
435 #ifndef GLIB_DISPATCHER_BROKEN
436                 done_connection=signal_done_.connect(mem_fun(*this,&AsyncRenderer::stop));
437 #endif
438
439                 render_thread=Glib::Thread::create(
440                         sigc::mem_fun(*this,&AsyncRenderer::render_target),
441 #if REJOIN_ON_STOP
442                         true
443 #else
444                         false
445 #endif
446                 );
447                 assert(render_thread);
448         }
449         else
450         {
451                 stop();
452         }
453 }
454
455 void
456 AsyncRenderer::render_target()
457 {
458         etl::handle<Target> target(AsyncRenderer::target);
459
460         if(target && target->render())
461         {
462                 success=true;
463         }
464         else
465         {
466                 error=true;
467 #ifndef REJOIN_ON_STOP
468                 return;
469 #endif
470         }
471
472         if(mutex.trylock())
473         {
474 #ifdef GLIB_DISPATCHER_BROKEN
475                 done_connection=Glib::signal_timeout().connect(
476                         sigc::bind_return(
477                                 mem_fun(*this,&AsyncRenderer::stop),
478                                 false
479                         )
480                         ,0
481                 );
482 #else
483                 signal_done_.emit();
484 #endif
485                 mutex.unlock();
486         }
487 }