Coverage Report

Created: 2026-02-09 06:54

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