Use a checkbox in the gui (Toolbox > File > Setup > Misc) for single_threaded, rather...
[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 "app.h"
34 #include <glibmm/thread.h>
35 #include <glibmm/dispatcher.h>
36
37 #ifdef HAVE_UNISTD_H
38 #include <unistd.h>
39 #endif
40
41 #ifdef HAVE_SYS_TYPES_H
42 #include <sys/types.h>
43 #endif
44
45 #ifdef HAVE_SYS_WAIT_H
46 #include <sys/wait.h>
47 #endif
48
49 #ifdef HAVE_SIGNAL_H
50 #include <signal.h>
51 #endif
52
53 #include <synfig/general.h>
54 #include <ETL/clock>
55
56 #endif
57
58 /* === U S I N G =========================================================== */
59
60 using namespace std;
61 using namespace etl;
62 using namespace synfig;
63 using namespace studio;
64
65 #define BOREDOM_TIMEOUT         50
66
67 #define REJOIN_ON_STOP  1
68
69 // The Glib::Dispatcher class is broken as of Glibmm 2.4.5.
70 // Defining this macro enables the workaround.
71 #define GLIB_DISPATCHER_BROKEN 1
72
73 /* === C L A S S E S ======================================================= */
74
75 class AsyncTarget_Tile : public synfig::Target_Tile
76 {
77 public:
78         etl::handle<synfig::Target_Tile> warm_target;
79
80         struct tile_t
81         {
82                 Surface surface;
83                 int x,y;
84                 tile_t(const Surface& surface,int x, int y):
85                         surface(surface),
86                         x(x),y(y)
87                 {
88                 }
89         };
90         std::list<tile_t> tile_queue;
91         Glib::Mutex mutex;
92
93 #ifndef GLIB_DISPATCHER_BROKEN
94         Glib::Dispatcher tile_ready_signal;
95 #endif
96         Glib::Cond cond_tile_queue_empty;
97         bool alive_flag;
98
99         sigc::connection ready_connection;
100
101 public:
102         AsyncTarget_Tile(etl::handle<synfig::Target_Tile> warm_target):
103                 warm_target(warm_target)
104         {
105                 set_avoid_time_sync(warm_target->get_avoid_time_sync());
106                 set_tile_w(warm_target->get_tile_w());
107                 set_tile_h(warm_target->get_tile_h());
108                 set_canvas(warm_target->get_canvas());
109                 set_quality(warm_target->get_quality());
110                 set_remove_alpha(warm_target->get_remove_alpha());
111                 set_threads(warm_target->get_threads());
112                 set_clipping(warm_target->get_clipping());
113                 set_rend_desc(&warm_target->rend_desc());
114                 alive_flag=true;
115 #ifndef GLIB_DISPATCHER_BROKEN
116                 ready_connection=tile_ready_signal.connect(sigc::mem_fun(*this,&AsyncTarget_Tile::tile_ready));
117 #endif
118         }
119
120         ~AsyncTarget_Tile()
121         {
122                 ready_connection.disconnect();
123         }
124         void set_dead()
125         {
126                 Glib::Mutex::Lock lock(mutex, NotLock);
127                 if (!single_threaded()) lock.acquire();
128                 alive_flag=false;
129         }
130
131         virtual int total_tiles()const
132         {
133                 return warm_target->total_tiles();
134         }
135
136         virtual int next_tile(int& x, int& y)
137         {
138                 if(!alive_flag)
139                         return 0;
140
141                 return warm_target->next_tile(x,y);
142         }
143
144         virtual int next_frame(Time& time)
145         {
146                 if(!alive_flag)
147                         return 0;
148                 return warm_target->next_frame(time);
149         }
150
151         virtual bool start_frame(synfig::ProgressCallback *cb=0)
152         {
153                 if(!alive_flag)
154                         return false;
155                 return warm_target->start_frame(cb);
156         }
157
158         virtual bool add_tile(const synfig::Surface &surface, int gx, int gy)
159         {
160                 assert(surface);
161                 if(!alive_flag)
162                         return false;
163                 Glib::Mutex::Lock lock(mutex, NotLock);
164                 if (!single_threaded()) lock.acquire();
165                 tile_queue.push_back(tile_t(surface,gx,gy));
166                 if(tile_queue.size()==1)
167                 {
168 #ifdef GLIB_DISPATCHER_BROKEN
169                 ready_connection=Glib::signal_timeout().connect(
170                         sigc::bind_return(
171                                 sigc::mem_fun(*this,&AsyncTarget_Tile::tile_ready),
172                                 false
173                         )
174                         ,0
175                 );
176 #else
177                 tile_ready_signal();
178 #endif
179                 }
180
181                 return alive_flag;
182         }
183
184         void tile_ready()
185         {
186                 Glib::Mutex::Lock lock(mutex, NotLock);
187                 if (!single_threaded()) lock.acquire();
188                 if(!alive_flag)
189                 {
190                         tile_queue.clear();
191                         cond_tile_queue_empty.signal();
192                         return;
193                 }
194                 while(!tile_queue.empty() && alive_flag)
195                 {
196                         tile_t& tile(tile_queue.front());
197
198                         alive_flag=warm_target->add_tile(tile.surface,tile.x,tile.y);
199
200                         tile_queue.pop_front();
201                 }
202                 cond_tile_queue_empty.signal();
203         }
204
205         virtual void end_frame()
206         {
207                 if (!single_threaded())
208                 {
209                         while(alive_flag)
210                         {
211                                 Glib::Mutex::Lock lock(mutex);
212                                 if(!tile_queue.empty() && alive_flag)
213                                 {
214                                         if(cond_tile_queue_empty.timed_wait(mutex,Glib::TimeVal(0,BOREDOM_TIMEOUT)))
215                                                 break;
216                                 }
217                                 else
218                                         break;
219                         }
220                 }
221                 Glib::Mutex::Lock lock(mutex, NotLock);
222                 if (!single_threaded()) lock.acquire();
223                 if(!alive_flag)
224                         return;
225                 return warm_target->end_frame();
226         }
227 };
228
229
230
231 class AsyncTarget_Scanline : public synfig::Target_Scanline
232 {
233 public:
234         etl::handle<synfig::Target_Scanline> warm_target;
235
236         int scanline_;
237         Surface surface;
238
239         Glib::Mutex mutex;
240
241 #ifndef GLIB_DISPATCHER_BROKEN
242         Glib::Dispatcher frame_ready_signal;
243 #endif
244         Glib::Cond cond_frame_queue_empty;
245         bool alive_flag;
246         bool ready_next;
247         sigc::connection ready_connection;
248
249
250 public:
251         AsyncTarget_Scanline(etl::handle<synfig::Target_Scanline> warm_target):
252                 warm_target(warm_target)
253         {
254                 set_avoid_time_sync(warm_target->get_avoid_time_sync());
255                 set_canvas(warm_target->get_canvas());
256                 set_quality(warm_target->get_quality());
257                 set_remove_alpha(warm_target->get_remove_alpha());
258                 set_threads(warm_target->get_threads());
259                 set_rend_desc(&warm_target->rend_desc());
260                 alive_flag=true;
261 #ifndef GLIB_DISPATCHER_BROKEN
262                 ready_connection=frame_ready_signal.connect(sigc::mem_fun(*this,&AsyncTarget_Scanline::frame_ready));
263 #endif
264                 surface.set_wh(warm_target->rend_desc().get_w(),warm_target->rend_desc().get_h());
265         }
266
267         ~AsyncTarget_Scanline()
268         {
269                 ready_connection.disconnect();
270         }
271
272         virtual int next_frame(Time& time)
273         {
274                 if(!alive_flag)
275                         return 0;
276                 return warm_target->next_frame(time);
277
278         }
279
280         void set_dead()
281         {
282                 Glib::Mutex::Lock lock(mutex, NotLock);
283                 if (!single_threaded()) lock.acquire();
284                 alive_flag=false;
285         }
286
287         virtual bool start_frame(synfig::ProgressCallback */*cb*/=0)
288         {
289                 return alive_flag;
290         }
291
292         virtual void end_frame()
293         {
294                 {
295                         Glib::Mutex::Lock lock(mutex, NotLock);
296                         if (!single_threaded()) lock.acquire();
297
298                         if(!alive_flag)
299                                 return;
300                         ready_next=false;
301
302 #ifdef GLIB_DISPATCHER_BROKEN
303                 ready_connection=Glib::signal_timeout().connect(
304                         sigc::bind_return(
305                                 sigc::mem_fun(*this,&AsyncTarget_Scanline::frame_ready),
306                                 false
307                         )
308                         ,0
309                 );
310 #else
311                         frame_ready_signal();
312 #endif
313                 }
314
315                 if (!single_threaded())
316                         while(alive_flag && !ready_next)
317                         {
318                                 Glib::Mutex::Lock lock(mutex);
319                                 if(cond_frame_queue_empty.timed_wait(mutex,Glib::TimeVal(0,BOREDOM_TIMEOUT)))
320                                         break;
321                         }
322         }
323
324
325         virtual Color * start_scanline(int scanline)
326         {
327                 Glib::Mutex::Lock lock(mutex, NotLock);
328                 if (!single_threaded()) lock.acquire();
329
330                 return surface[scanline];
331         }
332
333         virtual bool end_scanline()
334         {
335                 return alive_flag;
336         }
337
338         void frame_ready()
339         {
340                 Glib::Mutex::Lock lock(mutex, NotLock);
341                 if (!single_threaded()) lock.acquire();
342                 if(alive_flag)
343                         alive_flag=warm_target->add_frame(&surface);
344                 if (!single_threaded()) cond_frame_queue_empty.signal();
345                 ready_next=true;
346         }
347 };
348
349 /* === G L O B A L S ======================================================= */
350
351 /* === P R O C E D U R E S ================================================= */
352
353 /* === M E T H O D S ======================================================= */
354
355 AsyncRenderer::AsyncRenderer(etl::handle<synfig::Target> target_,synfig::ProgressCallback *cb):
356         error(false),
357         success(false),
358         cb(cb),
359         updating(false)
360 {
361         render_thread=0;
362         if(etl::handle<synfig::Target_Tile>::cast_dynamic(target_))
363         {
364                 etl::handle<AsyncTarget_Tile> wrap_target(
365                         new AsyncTarget_Tile(etl::handle<synfig::Target_Tile>::cast_dynamic(target_))
366                 );
367
368                 signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Tile::set_dead));
369
370                 target=wrap_target;
371         }
372         else if(etl::handle<synfig::Target_Scanline>::cast_dynamic(target_))
373         {
374                 etl::handle<AsyncTarget_Scanline> wrap_target(
375                         new AsyncTarget_Scanline(
376                                 etl::handle<synfig::Target_Scanline>::cast_dynamic(target_)
377                         )
378                 );
379
380                 signal_stop_.connect(sigc::mem_fun(*wrap_target,&AsyncTarget_Scanline::set_dead));
381
382                 target=wrap_target;
383         }
384 }
385
386 AsyncRenderer::~AsyncRenderer()
387 {
388         stop();
389 }
390
391 void
392 AsyncRenderer::stop()
393 {
394         if(target)
395         {
396                 Glib::Mutex::Lock lock(mutex, NotLock);
397                 if (!single_threaded()) lock.acquire();
398                 done_connection.disconnect();
399
400                 if(render_thread)
401                 {
402                         signal_stop_();
403
404 #if REJOIN_ON_STOP
405                         if (!single_threaded()) render_thread->join();
406 #endif
407
408                         // Make sure all the dispatch crap is cleared out
409                         //Glib::MainContext::get_default()->iteration(false);
410
411                         if(success)
412                                 signal_success_();
413
414                         signal_finished_();
415
416                         target=0;
417                         render_thread=0;
418                 }
419         }
420 }
421
422 void
423 AsyncRenderer::pause()
424 {
425 }
426
427 void
428 AsyncRenderer::resume()
429 {
430 }
431
432 void
433 AsyncRenderer::start()
434 {
435         done_connection=Glib::signal_timeout().connect(
436                 sigc::bind_return(
437                         mem_fun(*this,&AsyncRenderer::start_),
438                         false
439                 )
440                 ,50
441         );
442 }
443
444 void
445 AsyncRenderer::rendering_progress()
446 {
447         updating = true;
448         while(studio::App::events_pending()) studio::App::iteration(false);
449         updating = false;
450 }
451
452 void
453 AsyncRenderer::start_()
454 {
455         error=false;success=false;
456         if(target)
457         {
458 #ifndef GLIB_DISPATCHER_BROKEN
459                 done_connection=signal_done_.connect(mem_fun(*this,&AsyncRenderer::stop));
460 #endif
461
462                 if (single_threaded())
463                 {
464                         synfig::info("%s:%d rendering in the same thread", __FILE__, __LINE__);
465                         target->signal_progress().connect(sigc::mem_fun(this,&AsyncRenderer::rendering_progress));
466                         render_thread = (Glib::Thread*)1;
467                         render_target();
468                 }
469                 else
470                 {
471                         render_thread=Glib::Thread::create(
472                                 sigc::mem_fun(*this,&AsyncRenderer::render_target),
473 #if REJOIN_ON_STOP
474                                 true
475 #else
476                                 false
477 #endif
478                                 );
479                         assert(render_thread);
480                 }
481         }
482         else
483         {
484                 stop();
485         }
486 }
487
488 void
489 AsyncRenderer::render_target()
490 {
491         etl::handle<Target> target(AsyncRenderer::target);
492
493         if(target && target->render())
494         {
495                 success=true;
496         }
497         else
498         {
499                 error=true;
500 #ifndef REJOIN_ON_STOP
501                 return;
502 #endif
503         }
504
505         if (single_threaded() || mutex.trylock())
506         {
507 #ifdef GLIB_DISPATCHER_BROKEN
508                 done_connection=Glib::signal_timeout().connect(
509                         sigc::bind_return(
510                                 mem_fun(*this,&AsyncRenderer::stop),
511                                 false
512                         )
513                         ,0
514                 );
515 #else
516                 signal_done_.emit();
517 #endif
518                 if (!single_threaded()) mutex.unlock();
519         }
520 }