1 /* aynchronous screen sink
2 *
3 * 1/1/10
4 * - from im_render.c
5 * 25/11/10
6 * - in synchronous mode, use a single region for input and save huge
7 * mem use
8 * 20/1/14
9 * - bg render thread quits on shutdown
10 * 1/12/15
11 * - don't do anything to out or mask after they have closed
12 * - only run the bg render thread when there's work to do
13 */
14
15 /*
16
17 This file is part of VIPS.
18
19 VIPS is free software; you can redistribute it and/or modify
20 it under the terms of the GNU Lesser General Public License as published by
21 the Free Software Foundation; either version 2 of the License, or
22 (at your option) any later version.
23
24 This program is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 GNU Lesser General Public License for more details.
28
29 You should have received a copy of the GNU Lesser General Public License
30 along with this program; if not, write to the Free Software
31 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
32 02110-1301 USA
33
34 */
35
36 /*
37
38 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
39
40 */
41
42 /* Verbose debugging output.
43 #define VIPS_DEBUG
44 */
45
46 /* Trace allocate/free.
47 #define VIPS_DEBUG_AMBER
48 */
49
50 /* Trace reschedule
51 #define VIPS_DEBUG_GREEN
52 */
53
54 /* Trace serious problems.
55 #define VIPS_DEBUG_RED
56 */
57
58 #ifdef HAVE_CONFIG_H
59 #include <config.h>
60 #endif /*HAVE_CONFIG_H*/
61 #include <vips/intl.h>
62
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <math.h>
67 #ifdef HAVE_UNISTD_H
68 #include <unistd.h>
69 #endif /*HAVE_UNISTD_H*/
70
71 #include <vips/vips.h>
72 #include <vips/thread.h>
73 #include <vips/internal.h>
74 #include <vips/debug.h>
75
76 #ifdef VIPS_DEBUG_AMBER
77 static int render_num_renders = 0;
78 #endif /*VIPS_DEBUG_AMBER*/
79
80 /* A tile in our cache.
81 */
82 typedef struct {
83 struct _Render *render;
84
85 VipsRect area; /* Place here (unclipped) */
86 VipsRegion *region; /* VipsRegion with the pixels */
87
88 /* The tile contains calculated pixels. Though the region may have been
89 * invalidated behind our backs: we have to check that too.
90 */
91 gboolean painted;
92
93 /* The tile is on the dirty list. This saves us having to search the
94 * dirty list all the time.
95 */
96 gboolean dirty;
97
98 /* Time of last use, for LRU flush
99 */
100 int ticks;
101 } Tile;
102
103 /* Per-call state.
104 */
105 typedef struct _Render {
106 /* Reference count this, since we use these things from several
107 * threads. We can't easily use the gobject ref count system since we
108 * need a lock around operations.
109 */
110 #if GLIB_CHECK_VERSION( 2, 58, 0 )
111 gatomicrefcount ref_count;
112 #else
113 int ref_count;
114 GMutex *ref_count_lock;
115 #endif
116
117 /* Parameters.
118 */
119 VipsImage *in; /* Image we render */
120 VipsImage *out; /* Write tiles here on demand */
121 VipsImage *mask; /* Set valid pixels here */
122 int tile_width; /* Tile size */
123 int tile_height;
124 int max_tiles; /* Maximum number of tiles */
125 int priority; /* Larger numbers done sooner */
126 VipsSinkNotify notify; /* Tell caller about paints here */
127 void *a;
128
129 /* Lock here before reading or modifying the tile structure.
130 */
131 GMutex *lock;
132
133 /* Tile cache.
134 */
135 GSList *all; /* All our tiles */
136 int ntiles; /* Number of tiles */
137 int ticks; /* Inc. on each access ... used for LRU */
138
139 /* List of dirty tiles. Most recent at the front.
140 */
141 GSList *dirty;
142
143 /* Hash of tiles with positions. Tiles can be dirty or painted.
144 */
145 GHashTable *tiles;
146
147 /* A shutdown flag. If ->out or ->mask close, we must no longer do
148 * anything to them until we shut down too.
149 */
150 gboolean shutdown;
151 } Render;
152
153 /* Our per-thread state.
154 */
155 typedef struct _RenderThreadState {
156 VipsThreadState parent_object;
157
158 /* The tile that should be calculated.
159 */
160 Tile *tile;
161 } RenderThreadState;
162
163 typedef struct _RenderThreadStateClass {
164 VipsThreadStateClass parent_class;
165
166 } RenderThreadStateClass;
167
168 G_DEFINE_TYPE( RenderThreadState, render_thread_state, VIPS_TYPE_THREAD_STATE );
169
170 /* The BG thread which sits waiting to do some calculations, and the semaphore
171 * it waits on holding the number of renders with dirty tiles.
172 */
173 static GThread *render_thread = NULL;
174
175 /* Set this to ask the render thread to quit.
176 */
177 static gboolean render_kill = FALSE;
178
179 /* All the renders with dirty tiles, and a semaphore that the bg render thread
180 * waits on.
181 */
182 static GMutex *render_dirty_lock = NULL;
183 static GSList *render_dirty_all = NULL;
184 static VipsSemaphore n_render_dirty_sem;
185
186 /* Set this to make the bg thread stop and reschedule.
187 */
188 static gboolean render_reschedule = FALSE;
189
190 static void
render_thread_state_class_init(RenderThreadStateClass * class)191 render_thread_state_class_init( RenderThreadStateClass *class )
192 {
193 VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class );
194
195 object_class->nickname = "renderthreadstate";
196 object_class->description = _( "per-thread state for render" );
197 }
198
199 static void
render_thread_state_init(RenderThreadState * state)200 render_thread_state_init( RenderThreadState *state )
201 {
202 state->tile = NULL;
203 }
204
205 static VipsThreadState *
render_thread_state_new(VipsImage * im,void * a)206 render_thread_state_new( VipsImage *im, void *a )
207 {
208 return( VIPS_THREAD_STATE( vips_object_new(
209 render_thread_state_get_type(),
210 vips_thread_state_set, im, a ) ) );
211 }
212
213 static void *
tile_free(Tile * tile,void * a,void * b)214 tile_free( Tile *tile, void *a, void *b )
215 {
216 VIPS_DEBUG_MSG_AMBER( "tile_free\n" );
217
218 VIPS_UNREF( tile->region );
219 g_free( tile );
220
221 return( NULL );
222 }
223
224 static int
render_free(Render * render)225 render_free( Render *render )
226 {
227 VIPS_DEBUG_MSG_AMBER( "render_free: %p\n", render );
228
229 #if GLIB_CHECK_VERSION( 2, 58, 0 )
230 g_assert ( g_atomic_ref_count_compare( &render->ref_count, 0 ) );
231 #else
232 g_assert( render->ref_count == 0 );
233 #endif
234
235 g_mutex_lock( render_dirty_lock );
236 if( g_slist_find( render_dirty_all, render ) ) {
237 render_dirty_all = g_slist_remove( render_dirty_all, render );
238
239 /* We don't need to adjust the semaphore: if it's too high,
240 * the render thread will just loop and decrement next time
241 * render_dirty_all is NULL.
242 */
243 }
244 g_mutex_unlock( render_dirty_lock );
245
246 #if !GLIB_CHECK_VERSION( 2, 58, 0 )
247 vips_g_mutex_free( render->ref_count_lock );
248 #endif
249 vips_g_mutex_free( render->lock );
250
251 vips_slist_map2( render->all, (VipsSListMap2Fn) tile_free, NULL, NULL );
252 VIPS_FREEF( g_slist_free, render->all );
253 render->ntiles = 0;
254 VIPS_FREEF( g_slist_free, render->dirty );
255 VIPS_FREEF( g_hash_table_destroy, render->tiles );
256
257 VIPS_UNREF( render->in );
258
259 g_free( render );
260
261 #ifdef VIPS_DEBUG_AMBER
262 render_num_renders -= 1;
263 #endif /*VIPS_DEBUG_AMBER*/
264
265 return( 0 );
266 }
267
268 /* Ref and unref a Render ... free on last unref.
269 */
270 static int
render_ref(Render * render)271 render_ref( Render *render )
272 {
273 #if GLIB_CHECK_VERSION( 2, 58, 0 )
274 g_assert( !g_atomic_ref_count_compare( &render->ref_count, 0 ) );
275 g_atomic_ref_count_inc( &render->ref_count );
276 #else
277 g_mutex_lock( render->ref_count_lock );
278 g_assert( render->ref_count != 0 );
279 render->ref_count += 1;
280 g_mutex_unlock( render->ref_count_lock );
281 #endif
282
283 return( 0 );
284 }
285
286 static int
render_unref(Render * render)287 render_unref( Render *render )
288 {
289 int kill;
290
291 #if GLIB_CHECK_VERSION( 2, 58, 0 )
292 g_assert( !g_atomic_ref_count_compare( &render->ref_count, 0 ) );
293 kill = g_atomic_ref_count_dec( &render->ref_count );
294 #else
295 g_mutex_lock( render->ref_count_lock );
296 g_assert( render->ref_count > 0 );
297 render->ref_count -= 1;
298 kill = render->ref_count == 0;
299 g_mutex_unlock( render->ref_count_lock );
300 #endif
301
302 if( kill )
303 render_free( render );
304
305 return( 0 );
306 }
307
308 /* Get the next tile to paint off the dirty list.
309 */
310 static Tile *
render_tile_dirty_get(Render * render)311 render_tile_dirty_get( Render *render )
312 {
313 Tile *tile;
314
315 if( !render->dirty )
316 tile = NULL;
317 else {
318 tile = (Tile *) render->dirty->data;
319 g_assert( tile->dirty );
320 render->dirty = g_slist_remove( render->dirty, tile );
321 tile->dirty = FALSE;
322 }
323
324 return( tile );
325 }
326
327 /* Pick a dirty tile to reuse. We could potentially get the tile that
328 * render_work() is working on in the background :-( but I don't think we'll
329 * get a crash, just a mis-paint. It should be vanishingly impossible anyway.
330 */
331 static Tile *
render_tile_dirty_reuse(Render * render)332 render_tile_dirty_reuse( Render *render )
333 {
334 Tile *tile;
335
336 if( !render->dirty )
337 tile = NULL;
338 else {
339 tile = (Tile *) g_slist_last( render->dirty )->data;
340 render->dirty = g_slist_remove( render->dirty, tile );
341 g_assert( tile->dirty );
342 tile->dirty = FALSE;
343
344 VIPS_DEBUG_MSG( "render_tile_get_dirty_reuse: "
345 "reusing dirty %p\n", tile );
346 }
347
348 return( tile );
349 }
350
351 /* Add a tile to the dirty list.
352 */
353 static void
tile_dirty_set(Tile * tile)354 tile_dirty_set( Tile *tile )
355 {
356 Render *render = tile->render;
357
358 if( !tile->dirty ) {
359 g_assert( !g_slist_find( render->dirty, tile ) );
360 render->dirty = g_slist_prepend( render->dirty, tile );
361 tile->dirty = TRUE;
362 tile->painted = FALSE;
363 }
364 else
365 g_assert( g_slist_find( render->dirty, tile ) );
366 }
367
368 /* Bump a tile to the front of the dirty list, if it's there.
369 */
370 static void
tile_dirty_bump(Tile * tile)371 tile_dirty_bump( Tile *tile )
372 {
373 Render *render = tile->render;
374
375 if( tile->dirty ) {
376 g_assert( g_slist_find( render->dirty, tile ) );
377
378 render->dirty = g_slist_remove( render->dirty, tile );
379 render->dirty = g_slist_prepend( render->dirty, tile );
380 }
381 else
382 g_assert( !g_slist_find( render->dirty, tile ) );
383 }
384
385 static int
render_allocate(VipsThreadState * state,void * a,gboolean * stop)386 render_allocate( VipsThreadState *state, void *a, gboolean *stop )
387 {
388 Render *render = (Render *) a;
389 RenderThreadState *rstate = (RenderThreadState *) state;
390 Tile *tile;
391
392 g_mutex_lock( render->lock );
393
394 if( render_reschedule ||
395 !(tile = render_tile_dirty_get( render )) ) {
396 VIPS_DEBUG_MSG_GREEN( "render_allocate: stopping\n" );
397 *stop = TRUE;
398 rstate->tile = NULL;
399 }
400 else
401 rstate->tile = tile;
402
403 g_mutex_unlock( render->lock );
404
405 return( 0 );
406 }
407
408 static int
render_work(VipsThreadState * state,void * a)409 render_work( VipsThreadState *state, void *a )
410 {
411 Render *render = (Render *) a;
412 RenderThreadState *rstate = (RenderThreadState *) state;
413 Tile *tile = rstate->tile;
414
415 g_assert( tile );
416
417 VIPS_DEBUG_MSG( "calculating tile %p %dx%d\n",
418 tile, tile->area.left, tile->area.top );
419
420 if( vips_region_prepare_to( state->reg, tile->region,
421 &tile->area, tile->area.left, tile->area.top ) ) {
422 VIPS_DEBUG_MSG_RED( "render_work: "
423 "vips_region_prepare_to() failed: %s\n",
424 vips_error_buffer() );
425 return( -1 );
426 }
427 tile->painted = TRUE;
428
429 if( !render->shutdown &&
430 render->notify )
431 render->notify( render->out, &tile->area, render->a );
432
433 return( 0 );
434 }
435
436 static void render_dirty_put( Render *render );
437
438 /* Called from vips_shutdown().
439 */
440 void
vips__render_shutdown(void)441 vips__render_shutdown( void )
442 {
443 /* We may come here without having inited.
444 */
445 if( render_dirty_lock ) {
446 g_mutex_lock( render_dirty_lock );
447
448 if( render_thread ) {
449 GThread *thread;
450
451 thread = render_thread;
452 render_reschedule = TRUE;
453 render_kill = TRUE;
454
455 g_mutex_unlock( render_dirty_lock );
456
457 vips_semaphore_up( &n_render_dirty_sem );
458
459 (void) vips_g_thread_join( thread );
460 }
461 else
462 g_mutex_unlock( render_dirty_lock );
463
464 VIPS_FREEF( vips_g_mutex_free, render_dirty_lock );
465 vips_semaphore_destroy( &n_render_dirty_sem );
466 }
467 }
468
469 static int
render_dirty_sort(Render * a,Render * b,void * user_data)470 render_dirty_sort( Render *a, Render *b, void *user_data )
471 {
472 return( b->priority - a->priority );
473 }
474
475 /* Add to the jobs list, if it has work to be done.
476 */
477 static void
render_dirty_put(Render * render)478 render_dirty_put( Render *render )
479 {
480 g_mutex_lock( render_dirty_lock );
481
482 if( render->dirty ) {
483 if( !g_slist_find( render_dirty_all, render ) ) {
484 render_dirty_all = g_slist_prepend( render_dirty_all,
485 render );
486 render_dirty_all = g_slist_sort( render_dirty_all,
487 (GCompareFunc) render_dirty_sort );
488
489 /* Tell the bg render thread we have one more dirty
490 * render on there.
491 */
492 vips_semaphore_up( &n_render_dirty_sem );
493 }
494 }
495
496 g_mutex_unlock( render_dirty_lock );
497 }
498
499 static guint
tile_hash(gconstpointer key)500 tile_hash( gconstpointer key )
501 {
502 VipsRect *rect = (VipsRect *) key;
503
504 int x = rect->left / rect->width;
505 int y = rect->top / rect->height;
506
507 return( x << 16 ^ y );
508 }
509
510 static gboolean
tile_equal(gconstpointer a,gconstpointer b)511 tile_equal( gconstpointer a, gconstpointer b )
512 {
513 VipsRect *rect1 = (VipsRect *) a;
514 VipsRect *rect2 = (VipsRect *) b;
515
516 return( rect1->left == rect2->left &&
517 rect1->top == rect2->top );
518 }
519
520 static void
render_close_cb(VipsImage * image,Render * render)521 render_close_cb( VipsImage *image, Render *render )
522 {
523 VIPS_DEBUG_MSG_AMBER( "render_close_cb\n" );
524
525 /* The output image or mask are closing. This render will stick
526 * around for a while, since threads can still be running, but it
527 * must no longer reference ->out or ->mask (for example, invalidating
528 * them).
529 */
530 render->shutdown = TRUE;
531
532 render_unref( render );
533
534 /* If this render is being worked on, we want to jog the bg thread,
535 * make it drop it's ref and think again.
536 */
537 VIPS_DEBUG_MSG_GREEN( "render_close_cb: reschedule\n" );
538 render_reschedule = TRUE;
539 }
540
541 static Render *
render_new(VipsImage * in,VipsImage * out,VipsImage * mask,int tile_width,int tile_height,int max_tiles,int priority,VipsSinkNotify notify,void * a)542 render_new( VipsImage *in, VipsImage *out, VipsImage *mask,
543 int tile_width, int tile_height,
544 int max_tiles,
545 int priority,
546 VipsSinkNotify notify, void *a )
547 {
548 Render *render;
549
550 /* Don't use auto-free for render, we do our own lifetime management
551 * with _ref() and _unref().
552 */
553 if( !(render = VIPS_NEW( NULL, Render )) )
554 return( NULL );
555
556 /* render must hold a ref to in. This is dropped in render_free().
557 */
558 g_object_ref( in );
559
560 #if GLIB_CHECK_VERSION( 2, 58, 0 )
561 g_atomic_ref_count_init( &render->ref_count );
562 #else
563 render->ref_count = 1;
564 render->ref_count_lock = vips_g_mutex_new();
565 #endif
566
567 render->in = in;
568 render->out = out;
569 render->mask = mask;
570 render->tile_width = tile_width;
571 render->tile_height = tile_height;
572 render->max_tiles = max_tiles;
573 render->priority = priority;
574 render->notify = notify;
575 render->a = a;
576
577 render->lock = vips_g_mutex_new();
578
579 render->all = NULL;
580 render->ntiles = 0;
581 render->ticks = 0;
582
583 render->tiles = g_hash_table_new( tile_hash, tile_equal );
584
585 render->dirty = NULL;
586
587 render->shutdown = FALSE;
588
589 /* Both out and mask must close before we can free the render.
590 */
591 g_signal_connect( out, "close",
592 G_CALLBACK( render_close_cb ), render );
593
594 if( mask ) {
595 g_signal_connect( mask, "close",
596 G_CALLBACK( render_close_cb ), render );
597 render_ref( render );
598 }
599
600 VIPS_DEBUG_MSG_AMBER( "render_new: %p\n", render );
601
602 #ifdef VIPS_DEBUG_AMBER
603 render_num_renders += 1;
604 #endif /*VIPS_DEBUG_AMBER*/
605
606 return( render );
607 }
608
609 /* Make a Tile.
610 */
611 static Tile *
tile_new(Render * render)612 tile_new( Render *render )
613 {
614 Tile *tile;
615
616 VIPS_DEBUG_MSG_AMBER( "tile_new\n" );
617
618 /* Don't use auto-free: we need to make sure we free the tile after
619 * Render.
620 */
621 if( !(tile = VIPS_NEW( NULL, Tile )) )
622 return( NULL );
623
624 tile->render = render;
625 tile->area.left = 0;
626 tile->area.top = 0;
627 tile->area.width = render->tile_width;
628 tile->area.height = render->tile_height;
629 tile->region = NULL;
630 tile->painted = FALSE;
631 tile->dirty = FALSE;
632 tile->ticks = render->ticks;
633
634 if( !(tile->region = vips_region_new( render->in )) ) {
635 (void) tile_free( tile, NULL, NULL );
636 return( NULL );
637 }
638
639 render->all = g_slist_prepend( render->all, tile );
640 render->ntiles += 1;
641
642 return( tile );
643 }
644
645 /* Search the cache for a tile by position.
646 */
647 static Tile *
render_tile_lookup(Render * render,VipsRect * area)648 render_tile_lookup( Render *render, VipsRect *area )
649 {
650 return( (Tile *) g_hash_table_lookup( render->tiles, area ) );
651 }
652
653 /* Add a new tile to the table.
654 */
655 static void
render_tile_add(Tile * tile,VipsRect * area)656 render_tile_add( Tile *tile, VipsRect *area )
657 {
658 Render *render = tile->render;
659
660 g_assert( !render_tile_lookup( render, area ) );
661
662 tile->area = *area;
663 tile->painted = FALSE;
664
665 /* Ignore buffer allocate errors, there's not much we could do with
666 * them.
667 */
668 if( vips_region_buffer( tile->region, &tile->area ) )
669 VIPS_DEBUG_MSG_RED( "render_tile_add: "
670 "buffer allocate failed\n" );
671
672 g_hash_table_insert( render->tiles, &tile->area, tile );
673 }
674
675 /* Move a tile to a new position.
676 */
677 static void
render_tile_move(Tile * tile,VipsRect * area)678 render_tile_move( Tile *tile, VipsRect *area )
679 {
680 Render *render = tile->render;
681
682 g_assert( render_tile_lookup( render, &tile->area ) );
683
684 if( tile->area.left != area->left ||
685 tile->area.top != area->top ) {
686 g_assert( !render_tile_lookup( render, area ) );
687
688 g_hash_table_remove( render->tiles, &tile->area );
689 render_tile_add( tile, area );
690 }
691 }
692
693 /* We've looked at a tile ... bump to end of LRU and front of dirty.
694 */
695 static void
tile_touch(Tile * tile)696 tile_touch( Tile *tile )
697 {
698 Render *render = tile->render;
699
700 tile->ticks = render->ticks;
701 render->ticks += 1;
702 tile_dirty_bump( tile );
703 }
704
705 /* Queue a tile for calculation.
706 */
707 static void
tile_queue(Tile * tile,VipsRegion * reg)708 tile_queue( Tile *tile, VipsRegion *reg )
709 {
710 Render *render = tile->render;
711
712 VIPS_DEBUG_MSG( "tile_queue: adding tile %p %dx%d to dirty\n",
713 tile, tile->area.left, tile->area.top );
714
715 tile->painted = FALSE;
716 tile_touch( tile );
717
718 if( render->notify ) {
719 /* Add to the list of renders with dirty tiles. The bg
720 * thread will pick it up and paint it. It can be already on
721 * the dirty list.
722 */
723 tile_dirty_set( tile );
724 render_dirty_put( render );
725 }
726 else {
727 /* no notify ... paint the tile ourselves
728 * sychronously. No need to notify the client since they'll
729 * never see black tiles.
730 */
731 VIPS_DEBUG_MSG( "tile_queue: "
732 "painting tile %p %dx%d synchronously\n",
733 tile, tile->area.left, tile->area.top );
734
735 /* While we're computing, let other threads use the cache.
736 * This tile won't get pulled out from under us since it's not
737 * marked as "painted", and it's not on the dirty list.
738 */
739 g_mutex_unlock( render->lock );
740
741 if( vips_region_prepare_to( reg, tile->region,
742 &tile->area, tile->area.left, tile->area.top ) )
743 VIPS_DEBUG_MSG_RED( "tile_queue: prepare failed\n" );
744
745 g_mutex_lock( render->lock );
746
747 tile->painted = TRUE;
748 }
749 }
750
751 static void
tile_test_clean_ticks(VipsRect * key,Tile * value,Tile ** best)752 tile_test_clean_ticks( VipsRect *key, Tile *value, Tile **best )
753 {
754 if( value->painted )
755 if( !*best || value->ticks < (*best)->ticks )
756 *best = value;
757 }
758
759 /* Pick a painted tile to reuse. Search for LRU (slow!).
760 */
761 static Tile *
render_tile_get_painted(Render * render)762 render_tile_get_painted( Render *render )
763 {
764 Tile *tile;
765
766 tile = NULL;
767 g_hash_table_foreach( render->tiles,
768 (GHFunc) tile_test_clean_ticks, &tile );
769
770 if( tile ) {
771 VIPS_DEBUG_MSG( "render_tile_get_painted: "
772 "reusing painted %p\n", tile );
773 }
774
775 return( tile );
776 }
777
778 /* Ask for an area of calculated pixels. Get from cache, request calculation,
779 * or if we've no threads or no notify, calculate immediately.
780 */
781 static Tile *
render_tile_request(Render * render,VipsRegion * reg,VipsRect * area)782 render_tile_request( Render *render, VipsRegion *reg, VipsRect *area )
783 {
784 Tile *tile;
785
786 VIPS_DEBUG_MSG( "render_tile_request: asking for %dx%d\n",
787 area->left, area->top );
788
789 if( (tile = render_tile_lookup( render, area )) ) {
790 /* We already have a tile at this position. If it's invalid,
791 * ask for a repaint.
792 */
793 if( tile->region->invalid )
794 tile_queue( tile, reg );
795 else
796 tile_touch( tile );
797 }
798 else if( render->ntiles < render->max_tiles ||
799 render->max_tiles == -1 ) {
800 /* We have fewer tiles than teh max. We can just make a new
801 * tile.
802 */
803 if( !(tile = tile_new( render )) )
804 return( NULL );
805
806 render_tile_add( tile, area );
807
808 tile_queue( tile, reg );
809 }
810 else {
811 /* Need to reuse a tile. Try for an old painted tile first,
812 * then if that fails, reuse a dirty tile.
813 */
814 if( !(tile = render_tile_get_painted( render )) &&
815 !(tile = render_tile_dirty_reuse( render )) ) {
816 VIPS_DEBUG_MSG( "render_tile_request: "
817 "no tiles to reuse\n" );
818 return( NULL );
819 }
820
821 render_tile_move( tile, area );
822
823 tile_queue( tile, reg );
824 }
825
826 return( tile );
827 }
828
829 /* Copy what we can from the tile into the region.
830 */
831 static void
tile_copy(Tile * tile,VipsRegion * to)832 tile_copy( Tile *tile, VipsRegion *to )
833 {
834 VipsRect ovlap;
835
836 /* Find common pixels.
837 */
838 vips_rect_intersectrect( &tile->area, &to->valid, &ovlap );
839 g_assert( !vips_rect_isempty( &ovlap ) );
840
841 /* If the tile is painted, copy over the pixels. Otherwise, fill with
842 * zero.
843 */
844 if( tile->painted && !tile->region->invalid ) {
845 int len = VIPS_IMAGE_SIZEOF_PEL( to->im ) * ovlap.width;
846
847 int y;
848
849 VIPS_DEBUG_MSG( "tile_copy: "
850 "copying calculated pixels for %p %dx%d\n",
851 tile, tile->area.left, tile->area.top );
852
853 for( y = ovlap.top; y < VIPS_RECT_BOTTOM( &ovlap ); y++ ) {
854 VipsPel *p = VIPS_REGION_ADDR( tile->region,
855 ovlap.left, y );
856 VipsPel *q = VIPS_REGION_ADDR( to, ovlap.left, y );
857
858 memcpy( q, p, len );
859 }
860 }
861 else {
862 VIPS_DEBUG_MSG( "tile_copy: zero filling for %p %dx%d\n",
863 tile, tile->area.left, tile->area.top );
864 vips_region_paint( to, &ovlap, 0 );
865 }
866 }
867
868 /* Loop over the output region, filling with data from cache.
869 */
870 static int
image_fill(VipsRegion * out,void * seq,void * a,void * b,gboolean * stop)871 image_fill( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop )
872 {
873 Render *render = (Render *) b;
874 int tile_width = render->tile_width;
875 int tile_height = render->tile_height;
876 VipsRegion *reg = (VipsRegion *) seq;
877 VipsRect *r = &out->valid;
878
879 int x, y;
880
881 /* Find top left of tiles we need.
882 */
883 int xs = (r->left / tile_width) * tile_width;
884 int ys = (r->top / tile_height) * tile_height;
885
886 VIPS_DEBUG_MSG( "image_fill: left = %d, top = %d, "
887 "width = %d, height = %d\n",
888 r->left, r->top, r->width, r->height );
889
890 g_mutex_lock( render->lock );
891
892 /*
893
894 FIXME ... if r fits inside a single tile, we could skip the
895 copy.
896
897 */
898
899 for( y = ys; y < VIPS_RECT_BOTTOM( r ); y += tile_height )
900 for( x = xs; x < VIPS_RECT_RIGHT( r ); x += tile_width ) {
901 VipsRect area;
902 Tile *tile;
903
904 area.left = x;
905 area.top = y;
906 area.width = tile_width;
907 area.height = tile_height;
908
909 tile = render_tile_request( render, reg, &area );
910 if( tile )
911 tile_copy( tile, out );
912 else
913 VIPS_DEBUG_MSG_RED( "image_fill: argh!\n" );
914 }
915
916 g_mutex_unlock( render->lock );
917
918 return( 0 );
919 }
920
921 /* The mask image is 255 / 0 for the state of painted for each tile.
922 */
923 static int
mask_fill(VipsRegion * out,void * seq,void * a,void * b,gboolean * stop)924 mask_fill( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop )
925 {
926 Render *render = (Render *) a;
927 int tile_width = render->tile_width;
928 int tile_height = render->tile_height;
929 VipsRect *r = &out->valid;
930
931 int x, y;
932
933 /* Find top left of tiles we need.
934 */
935 int xs = (r->left / tile_width) * tile_width;
936 int ys = (r->top / tile_height) * tile_height;
937
938 VIPS_DEBUG_MSG( "mask_fill: left = %d, top = %d, "
939 "width = %d, height = %d\n",
940 r->left, r->top, r->width, r->height );
941
942 g_mutex_lock( render->lock );
943
944 for( y = ys; y < VIPS_RECT_BOTTOM( r ); y += tile_height )
945 for( x = xs; x < VIPS_RECT_RIGHT( r ); x += tile_width ) {
946 VipsRect area;
947 Tile *tile;
948 int value;
949
950 area.left = x;
951 area.top = y;
952 area.width = tile_width;
953 area.height = tile_height;
954
955 tile = render_tile_lookup( render, &area );
956 value = (tile &&
957 tile->painted &&
958 !tile->region->invalid) ? 255 : 0;
959
960 /* Only mark painted tiles containing valid pixels.
961 */
962 vips_region_paint( out, &area, value );
963 }
964
965 g_mutex_unlock( render->lock );
966
967 return( 0 );
968 }
969
970 /* Get the first render with dirty tiles.
971 */
972 static Render *
render_dirty_get(void)973 render_dirty_get( void )
974 {
975 Render *render;
976
977 /* Wait for a render with dirty tiles.
978 */
979 vips_semaphore_down( &n_render_dirty_sem );
980
981 g_mutex_lock( render_dirty_lock );
982
983 /* Just take the head of the jobs list ... we sort when we add.
984 */
985 render = NULL;
986 if( render_dirty_all ) {
987 render = (Render *) render_dirty_all->data;
988
989 /* Ref the render to make sure it can't die while we're
990 * working on it.
991 */
992 render_ref( render );
993
994 render_dirty_all = g_slist_remove( render_dirty_all, render );
995 }
996
997 g_mutex_unlock( render_dirty_lock );
998
999 return( render );
1000 }
1001
1002 /* Loop for the background render manager thread.
1003 */
1004 static void *
render_thread_main(void * client)1005 render_thread_main( void *client )
1006 {
1007 Render *render;
1008
1009 while( !render_kill ) {
1010 VIPS_DEBUG_MSG_GREEN( "render_thread_main: "
1011 "threadpool start\n" );
1012
1013 render_reschedule = FALSE;
1014
1015 if( (render = render_dirty_get()) ) {
1016 if( vips_threadpool_run( render->in,
1017 render_thread_state_new,
1018 render_allocate,
1019 render_work,
1020 NULL,
1021 render ) )
1022 VIPS_DEBUG_MSG_RED( "render_thread_main: "
1023 "threadpool_run failed\n" );
1024
1025 VIPS_DEBUG_MSG_GREEN( "render_thread_main: "
1026 "threadpool return\n" );
1027
1028 /* Add back to the jobs list, if we need to.
1029 */
1030 render_dirty_put( render );
1031
1032 /* _get() does a ref to make sure we keep the render
1033 * alive during processing ... unref before we loop.
1034 * This can kill off the render.
1035 */
1036 render_unref( render );
1037 }
1038 }
1039
1040 /* We are exiting, so render_thread must now be NULL.
1041 */
1042 render_thread = NULL;
1043
1044 return( NULL );
1045 }
1046
1047 static void *
vips__sink_screen_once(void * data)1048 vips__sink_screen_once( void *data )
1049 {
1050 g_assert( !render_thread );
1051 g_assert( !render_dirty_lock );
1052
1053 render_dirty_lock = vips_g_mutex_new();
1054 vips_semaphore_init( &n_render_dirty_sem, 0, "n_render_dirty" );
1055
1056 /* Don't use vips__thread_execute() since this thread will only be
1057 * ended by vips_shutdown, and that isn't always called.
1058 */
1059 render_thread = vips_g_thread_new( "sink_screen",
1060 render_thread_main, NULL );
1061
1062 return( NULL );
1063 }
1064
1065 /**
1066 * vips_sink_screen: (method)
1067 * @in: input image
1068 * @out: (out): output image
1069 * @mask: mask image indicating valid pixels
1070 * @tile_width: tile width
1071 * @tile_height: tile height
1072 * @max_tiles: maximum tiles to cache
1073 * @priority: rendering priority
1074 * @notify_fn: (scope call) (nullable): pixels are ready notification callback
1075 * @a: (closure notify_fn) (nullable): client data for callback
1076 *
1077 * This operation renders @in in the background, making pixels available on
1078 * @out as they are calculated. The @notify_fn callback is run every time a new
1079 * set of pixels are available. Calculated pixels are kept in a cache with
1080 * tiles sized @tile_width by @tile_height pixels and with at most @max_tiles
1081 * tiles.
1082 * If @max_tiles is -1, the cache is of unlimited size (up to the maximum image
1083 * size).
1084 * The @mask image is a one-band uchar image and has 255 for pixels which are
1085 * currently in cache and 0 for uncalculated pixels.
1086 *
1087 * Only a single sink is calculated at any one time, though many may be
1088 * alive. Use @priority to indicate which renders are more important:
1089 * zero means normal
1090 * priority, negative numbers are low priority, positive numbers high
1091 * priority.
1092 *
1093 * Calls to vips_region_prepare() on @out return immediately and hold
1094 * whatever is
1095 * currently in cache for that #VipsRect (check @mask to see which parts of the
1096 * #VipsRect are valid). Any pixels in the #VipsRect which are not in
1097 * cache are added
1098 * to a queue, and the @notify_fn callback will trigger when those pixels are
1099 * ready.
1100 *
1101 * The @notify_fn callback is run from one of the background threads. In the
1102 * callback
1103 * you need to somehow send a message to the main thread that the pixels are
1104 * ready. In a glib-based application, this is easily done with g_idle_add().
1105 *
1106 * If @notify_fn is %NULL then vips_sink_screen() runs synchronously.
1107 * vips_region_prepare() on @out will always block until the pixels have been
1108 * calculated.
1109 *
1110 * See also: vips_tilecache(), vips_region_prepare(),
1111 * vips_sink_disc(), vips_sink().
1112 *
1113 * Returns: 0 on sucess, -1 on error.
1114 */
1115 int
vips_sink_screen(VipsImage * in,VipsImage * out,VipsImage * mask,int tile_width,int tile_height,int max_tiles,int priority,VipsSinkNotify notify_fn,void * a)1116 vips_sink_screen( VipsImage *in, VipsImage *out, VipsImage *mask,
1117 int tile_width, int tile_height,
1118 int max_tiles,
1119 int priority,
1120 VipsSinkNotify notify_fn, void *a )
1121 {
1122 static GOnce once = G_ONCE_INIT;
1123
1124 Render *render;
1125
1126 VIPS_ONCE( &once, vips__sink_screen_once, NULL );
1127
1128 if( tile_width <= 0 || tile_height <= 0 ||
1129 max_tiles < -1 ) {
1130 vips_error( "vips_sink_screen", "%s", _( "bad parameters" ) );
1131 return( -1 );
1132 }
1133
1134 if( vips_image_pio_input( in ) ||
1135 vips_image_pipelinev( out,
1136 VIPS_DEMAND_STYLE_SMALLTILE, in, NULL ) )
1137 return( -1 );
1138
1139 if( mask ) {
1140 if( vips_image_pipelinev( mask,
1141 VIPS_DEMAND_STYLE_SMALLTILE, in, NULL ) )
1142 return( -1 );
1143
1144 mask->Bands = 1;
1145 mask->BandFmt = VIPS_FORMAT_UCHAR;
1146 mask->Type = VIPS_INTERPRETATION_B_W;
1147 mask->Coding = VIPS_CODING_NONE;
1148 }
1149
1150 if( !(render = render_new( in, out, mask,
1151 tile_width, tile_height, max_tiles, priority, notify_fn, a )) )
1152 return( -1 );
1153
1154 VIPS_DEBUG_MSG( "vips_sink_screen: max = %d, %p\n", max_tiles, render );
1155
1156 if( vips_image_generate( out,
1157 vips_start_one, image_fill, vips_stop_one, in, render ) )
1158 return( -1 );
1159 if( mask &&
1160 vips_image_generate( mask,
1161 NULL, mask_fill, NULL, render, NULL ) )
1162 return( -1 );
1163
1164 return( 0 );
1165 }
1166
1167 int
vips__print_renders(void)1168 vips__print_renders( void )
1169 {
1170 int n_leaks;
1171
1172 n_leaks = 0;
1173
1174 #ifdef VIPS_DEBUG_AMBER
1175 if( render_num_renders > 0 ) {
1176 printf( "%d active renders\n", render_num_renders );
1177 n_leaks += render_num_renders;
1178 }
1179 #endif /*VIPS_DEBUG_AMBER*/
1180
1181 if( render_dirty_lock ) {
1182 g_mutex_lock( render_dirty_lock );
1183
1184 n_leaks += g_slist_length( render_dirty_all );
1185 if( render_dirty_all )
1186 printf( "dirty renders\n" );
1187
1188 g_mutex_unlock( render_dirty_lock );
1189 }
1190
1191 return( n_leaks );
1192 }
1193