/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 | } |