Coverage Report

Created: 2025-06-24 07:01

/src/ghostpdl/base/gxclthrd.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2001-2025 Artifex Software, Inc.
2
   All Rights Reserved.
3
4
   This software is provided AS-IS with no warranty, either express or
5
   implied.
6
7
   This software is distributed under license and may not be copied,
8
   modified or distributed except as expressly authorized under the terms
9
   of the license contained in the file LICENSE in this distribution.
10
11
   Refer to licensing information at http://www.artifex.com or contact
12
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
13
   CA 94129, USA, for further information.
14
*/
15
16
17
/* Command list - Support for multiple rendering threads */
18
#include "memory_.h"
19
#include "gx.h"
20
#include "gp.h"
21
#include "gpcheck.h"
22
#include "gxsync.h"
23
#include "gserrors.h"
24
#include "gxdevice.h"
25
#include "gsdevice.h"
26
#include "gxdevmem.h"           /* must precede gxcldev.h */
27
#include "gdevprn.h"            /* must precede gxcldev.h */
28
#include "gxcldev.h"
29
#include "gxgetbit.h"
30
#include "gdevplnx.h"
31
#include "gdevppla.h"
32
#include "gsmemory.h"
33
#include "gsmchunk.h"
34
#include "gxclthrd.h"
35
#include "gdevdevn.h"
36
#include "gsicc_cache.h"
37
#include "gsicc_manage.h"
38
#include "gstrans.h"
39
#include "gzht.h"   /* for gx_ht_cache_default_bits_size */
40
41
/* Forward reference prototypes */
42
static int clist_start_render_thread(gx_device *dev, int thread_index, int band);
43
static void clist_render_thread(void* param);
44
static void clist_render_thread_no_output_fn(void* param);
45
46
/*
47
        Notes on operation:
48
49
        We now have 2 mechanisms for rendering threads.
50
51
        Both mechanisms use a process_page procedure set to
52
        clist_process_page_mt so control arrives here to render the page
53
        for output.
54
55
        The interpreter calls the devices print_page function. Devices
56
        then call process_page with an options structure that inclues
57
        both a process_fn and an output_fn.
58
59
        In all cases that we care about here, process_page is set to
60
        clist_process_page_mt. For both mechanisms, this function sets
61
        up a pool of n worker threads all ready to render different
62
        bands of the file.
63
64
        This function then causes each worker to render a thread in
65
        turn (by calling process_fn, which calls back to getbits, which
66
        causes the band to be rendered to a buffer).
67
68
        Most devices that are just writing each band to an output file
69
        rely on 'in-order' calling. That is to say, they need to output
70
        the data from band 0, followed by that from band 1, etc. (or
71
        possibly band n-1, followed by band-2, etc).
72
73
        Such devices define an 'output_fn', which is called back with
74
        the results of each band in turn. We guarantee that this
75
        output_fn is called in the order it expects.
76
77
        The only downside to this way of working, is that threads can
78
        be sat around not working for long periods - especially if the
79
        different bands take different lengths of time to render.
80
81
        For example, imagine if we are running with 4 workers. We
82
        kick off rendering for band 0,1,2,3 at the same time. Whichever
83
        order those renderings finish in, we can only ever output them
84
        in the order 0,1,2,3.
85
86
        This means that if band 3 finishes early (perhaps its contents
87
        are less complex than the other bands), it will be sat there
88
        idle waiting for all the other bands to finish before it can
89
        output, and resume its next rendering.
90
91
        For devices that are not dependent on the order in which data
92
        becomes available, we therefore offer a second mechanism; by
93
        setting output_fn to NULL, we indicate that process_fn will
94
        not only render the page, it will 'output' it too, and handle
95
        the selection of which band to render next.
96
97
        In this mode, the render threads can always be kept busy,
98
        modulo any blocking that happens within the process_fn. For
99
        devices which can operate in this way, non-trivial speedups
100
        can be given.
101
*/
102
103
/* clone a device and set params and its chunk memory                   */
104
/* The chunk_base_mem MUST be thread safe                               */
105
/* Return NULL on error, or the cloned device with the dev->memory set  */
106
/* to the chunk allocator.                                              */
107
/* Exported for use by background printing.                             */
108
gx_device *
109
setup_device_and_mem_for_thread(gs_memory_t *chunk_base_mem, gx_device *dev, bool bg_print, gsicc_link_cache_t **cachep)
110
0
{
111
0
    int i, code;
112
0
    char fmode[4];
113
0
    gs_memory_t *thread_mem;
114
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
115
0
    gx_device_printer *pdev = (gx_device_printer *)dev;
116
0
    gx_device_clist_common *cdev = (gx_device_clist_common *)cldev;
117
0
    gx_device *ndev;
118
0
    gx_device_clist *ncldev;
119
0
    gx_device_clist_common *ncdev;
120
0
    gx_device_printer *npdev;
121
0
    gx_device *protodev;
122
0
    gs_c_param_list paramlist;
123
0
    gs_devn_params *pclist_devn_params;
124
0
    gx_device_buf_space_t buf_space;
125
0
    size_t min_buffer_space;
126
127
128
    /* Every thread will have a 'chunk allocator' to reduce the interaction
129
     * with the 'base' allocator which has 'mutex' (locking) protection.
130
     * This improves performance of the threads.
131
     */
132
0
    if ((code = gs_memory_chunk_wrap(&(thread_mem), chunk_base_mem )) < 0) {
133
0
        emprintf1(dev->memory, "chunk_wrap returned error code: %d\n", code);
134
0
        return NULL;
135
0
    }
136
    /* Find the prototype for this device (needed so we can copy from it) */
137
0
    for (i=0; (protodev = (gx_device *)gs_getdevice(i)) != NULL; i++)
138
0
        if (strcmp(protodev->dname, dev->dname) == 0)
139
0
            break;
140
141
    /* Clone the device from the prototype device */
142
0
    if (protodev == NULL ||
143
0
        (code = gs_copydevice((gx_device **) &ndev, protodev, thread_mem)) < 0 ||
144
0
        ndev == NULL) {       /* should only happen if copydevice failed */
145
0
        gs_memory_chunk_release(thread_mem);
146
0
        return NULL;
147
0
    }
148
0
    ncldev = (gx_device_clist *)ndev;
149
0
    ncdev = (gx_device_clist_common *)ndev;
150
0
    npdev = (gx_device_printer *)ndev;
151
0
    gx_device_fill_in_procs(ndev);
152
0
    ((gx_device_printer *)ncdev)->buffer_memory =
153
0
        ncdev->memory =
154
0
            ncdev->bandlist_memory =
155
0
               thread_mem;
156
0
    ndev->PageCount = dev->PageCount;       /* copy to prevent mismatch error */
157
0
    npdev->file = pdev->file;               /* For background printing when doing N copies with %d */
158
0
    strcpy((npdev->fname), (pdev->fname));
159
0
    ndev->color_info = dev->color_info;     /* copy before putdeviceparams */
160
0
    ndev->pad = dev->pad;
161
0
    ndev->log2_align_mod = dev->log2_align_mod;
162
0
    ndev->num_planar_planes = dev->num_planar_planes;
163
0
    ndev->icc_struct = NULL;
164
165
    /* If the device ICC profile (or proof) is OI_PROFILE, then that was not handled
166
     * by put/get params, and we cannot share the profiles between the 'parent' output device
167
     * and the devices created for each thread. Thus we also cannot share the icc_struct.
168
     * In this case we need to create a new icc_struct and clone the profiles.  The clone
169
     * operation also initializes some of the required data
170
     * We need to do this *before* the gs_getdeviceparams/gs_putdeviceparams so gs_putdeviceparams
171
     * will spot the same profile being used, and treat it as a no-op. Otherwise it will try to find
172
     * a profile with the 'special' name "OI_PROFILE" and throw an error.
173
     */
174
0
#define DEV_PROFILE_IS(DEV, PROFILE, MATCH) \
175
0
    ((DEV)->icc_struct != NULL &&\
176
0
     (DEV)->icc_struct->PROFILE != NULL &&\
177
0
     strcmp((DEV)->icc_struct->PROFILE->name, MATCH) == 0)
178
179
0
    if (bg_print ||
180
0
        !gscms_is_threadsafe() ||
181
0
        DEV_PROFILE_IS(dev, device_profile[GS_DEFAULT_DEVICE_PROFILE], OI_PROFILE) ||
182
0
        DEV_PROFILE_IS(dev, proof_profile, OI_PROFILE)) {
183
0
        ndev->icc_struct = gsicc_new_device_profile_array(ndev);
184
0
        if (!ndev->icc_struct) {
185
0
            emprintf1(ndev->memory,
186
0
                  "Error setting up device profile array, code=%d. Rendering threads not started.\n",
187
0
                  code);
188
0
            goto out_cleanup;
189
0
        }
190
0
        if ((code = gsicc_clone_profile(dev->icc_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE],
191
0
            &(ndev->icc_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE]), ndev->memory)) < 0) {
192
0
            emprintf1(dev->memory,
193
0
                "Error setting up device profile, code=%d. Rendering threads not started.\n",
194
0
                code);
195
0
            goto out_cleanup;
196
0
        }
197
0
        if (dev->icc_struct->proof_profile &&
198
0
           (code = gsicc_clone_profile(dev->icc_struct->proof_profile,
199
0
            &ndev->icc_struct->proof_profile, ndev->memory)) < 0) {
200
0
            emprintf1(dev->memory,
201
0
                "Error setting up proof profile, code=%d. Rendering threads not started.\n",
202
0
                code);
203
0
            goto out_cleanup;
204
205
0
        }
206
0
    }
207
0
    else {
208
        /* safe to share the icc_struct among threads */
209
0
        ndev->icc_struct = dev->icc_struct;  /* Set before put params */
210
0
        rc_increment(ndev->icc_struct);
211
0
    }
212
    /* get the current device parameters to put into the cloned device */
213
0
    gs_c_param_list_write(&paramlist, thread_mem);
214
0
    if ((code = gs_getdeviceparams(dev, (gs_param_list *)&paramlist)) < 0) {
215
0
        emprintf1(dev->memory,
216
0
                  "Error getting device params, code=%d. Rendering threads not started.\n",
217
0
                  code);
218
0
        goto out_cleanup;
219
0
    }
220
0
    gs_c_param_list_read(&paramlist);
221
0
    if ((code = gs_putdeviceparams(ndev, (gs_param_list *)&paramlist)) < 0)
222
0
        goto out_cleanup;
223
0
    gs_c_param_list_release(&paramlist);
224
225
    /* In the case of a separation device, we need to make sure we get the
226
       devn params copied over */
227
0
    pclist_devn_params = dev_proc(dev, ret_devn_params)(dev);
228
0
    if (pclist_devn_params != NULL) {
229
0
        code = devn_copy_params(dev, ndev);
230
0
        if (code < 0) {
231
#ifdef DEBUG /* suppress a warning on a release build */
232
            (void)gs_note_error(gs_error_VMerror);
233
#endif
234
0
            goto out_cleanup;
235
0
        }
236
0
    }
237
    /* Also make sure supports_devn is set correctly */
238
0
    ndev->icc_struct->supports_devn = cdev->icc_struct->supports_devn;
239
0
    ncdev->page_uses_transparency = cdev->page_uses_transparency;
240
0
    if_debug3m(gs_debug_flag_icc, cdev->memory,
241
0
               "[icc] MT clist device = "PRI_INTPTR" profile = "PRI_INTPTR" handle = "PRI_INTPTR"\n",
242
0
               (intptr_t)ncdev,
243
0
               (intptr_t)ncdev->icc_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE],
244
0
               (intptr_t)ncdev->icc_struct->device_profile[GS_DEFAULT_DEVICE_PROFILE]->profile_handle);
245
    /* If the device is_planar, then set the flag in the new_device and the procs */
246
0
    if ((ncdev->num_planar_planes = cdev->num_planar_planes))
247
0
        gdev_prn_set_procs_planar(ndev);
248
249
    /* Make sure that the ncdev BandHeight matches what we used when writing the clist, but
250
     * re-calculate the BandBufferSpace so we don't over-allocate (in case the page uses
251
     * transparency so that the BandHeight was reduced.)
252
     */
253
0
    ncdev->space_params.band = cdev->page_info.band_params;
254
0
    ncdev->space_params.banding_type = BandingAlways;
255
0
    code = npdev->printer_procs.buf_procs.size_buf_device
256
0
                (&buf_space, (gx_device *)ncdev, NULL, ncdev->space_params.band.BandHeight, false);
257
0
    min_buffer_space = clist_minimum_buffer(cdev->nbands);
258
0
    ncdev->space_params.band.BandBufferSpace = buf_space.bits + buf_space.line_ptrs;
259
    /* Check if the BandBufferSpace is large enough to allow us for clist writing */
260
    /* to prevent an error from gdev_prn_allocate_memory which checks that.       */
261
0
    if (min_buffer_space > ncdev->space_params.band.BandBufferSpace)
262
0
        ncdev->space_params.band.BandBufferSpace = min_buffer_space;
263
0
    ncdev->space_params.band.tile_cache_size = cdev->page_info.tile_cache_size; /* must be the same */
264
0
    ncdev->space_params.band.BandBufferSpace += cdev->page_info.tile_cache_size;
265
266
    /* gdev_prn_allocate_memory sets the clist for writing, creating new files.
267
     * We need  to unlink those files and open the main thread's files, then
268
     * reset the clist state for reading/rendering
269
     */
270
0
    if ((code = gdev_prn_allocate_memory(ndev, NULL, ndev->width, ndev->height)) < 0)
271
0
        goto out_cleanup;
272
273
0
    if (ncdev->page_info.tile_cache_size != cdev->page_info.tile_cache_size) {
274
0
        emprintf2(thread_mem,
275
0
                   "clist_setup_render_threads: tile_cache_size mismatch. New size=%"PRIdSIZE", should be %"PRIdSIZE"\n",
276
0
                   ncdev->page_info.tile_cache_size, cdev->page_info.tile_cache_size);
277
0
        goto out_cleanup;
278
0
    }
279
280
    /* close and unlink the temp files just created */
281
0
    ncdev->page_info.io_procs->fclose(ncdev->page_info.cfile, ncdev->page_info.cfname, true);
282
0
    ncdev->page_info.io_procs->fclose(ncdev->page_info.bfile, ncdev->page_info.bfname, true);
283
0
    ncdev->page_info.cfile = ncdev->page_info.bfile = NULL;
284
285
    /* open the main thread's files for this thread */
286
0
    strcpy(fmode, "r");                 /* read access for threads */
287
0
    strncat(fmode, gp_fmode_binary_suffix, 1);
288
0
    if ((code=cdev->page_info.io_procs->fopen(cdev->page_info.cfname, fmode, &ncdev->page_info.cfile,
289
0
                        thread_mem, thread_mem, true)) < 0 ||
290
0
         (code=cdev->page_info.io_procs->fopen(cdev->page_info.bfname, fmode, &ncdev->page_info.bfile,
291
0
                        thread_mem, thread_mem, false)) < 0)
292
0
        goto out_cleanup;
293
294
0
    strcpy((ncdev->page_info.cfname), (cdev->page_info.cfname));
295
0
    strcpy((ncdev->page_info.bfname), (cdev->page_info.bfname));
296
0
    clist_render_init(ncldev);      /* Initialize clist device for reading */
297
0
    ncdev->page_info.bfile_end_pos = cdev->page_info.bfile_end_pos;
298
299
    /* The threads are maintained until clist_finish_page.  At which
300
       point, the threads are torn down, the master clist reader device
301
       is changed to writer, and the icc_table and the icc_cache_cl freed */
302
0
    if (dev->icc_struct == ndev->icc_struct) {
303
    /* safe to share the link cache */
304
0
        ncdev->icc_cache_cl = cdev->icc_cache_cl;
305
0
        rc_increment(cdev->icc_cache_cl);   /* FIXME: needs to be incdemented safely */
306
0
    } else {
307
        /* each thread needs its own link cache */
308
0
        if (cachep != NULL) {
309
0
            if (*cachep == NULL) {
310
                /* We don't have one cached that we can reuse, so make one. */
311
0
                if ((*cachep = gsicc_cache_new(thread_mem->thread_safe_memory)) == NULL)
312
0
                    goto out_cleanup;
313
0
            }
314
0
            rc_increment(*cachep);
315
0
                ncdev->icc_cache_cl = *cachep;
316
0
        } else if ((ncdev->icc_cache_cl = gsicc_cache_new(thread_mem->thread_safe_memory)) == NULL)
317
0
            goto out_cleanup;
318
0
    }
319
0
    if (bg_print) {
320
0
        gx_device_clist_reader *ncrdev = (gx_device_clist_reader *)ncdev;
321
322
0
        if (cdev->icc_table != NULL) {
323
            /* This is a background printing thread, so it cannot share the icc_table  */
324
            /* since this probably was created with a GC'ed allocator and the bg_print */
325
            /* thread can't deal with the relocation. Free the cdev->icc_table and get */
326
            /* a new one from the clist.                                               */
327
0
            clist_free_icc_table(cdev->icc_table, cdev->memory);
328
0
            cdev->icc_table = NULL;
329
0
            if ((code = clist_read_icctable((gx_device_clist_reader *)ncdev)) < 0)
330
0
                goto out_cleanup;
331
0
        }
332
        /* Similarly for the color_usage_array, when the foreground device switches to */
333
        /* writer mode, the foreground's array will be freed.                          */
334
0
        if ((code = clist_read_color_usage_array(ncrdev)) < 0)
335
0
            goto out_cleanup;
336
0
    } else {
337
    /* Use the same profile table and color usage array in each thread */
338
0
        ncdev->icc_table = cdev->icc_table;   /* OK for multiple rendering threads */
339
0
        ((gx_device_clist_reader *)ncdev)->color_usage_array =
340
0
                ((gx_device_clist_reader *)cdev)->color_usage_array;
341
0
    }
342
    /* Needed for case when the target has cielab profile and pdf14 device
343
       has a RGB profile stored in the profile list of the clist */
344
0
    ncdev->trans_dev_icc_hash = cdev->trans_dev_icc_hash;
345
346
    /* success */
347
0
    return ndev;
348
349
0
out_cleanup:
350
    /* Close the file handles, but don't delete (unlink) the files */
351
0
    if (ncdev->page_info.bfile != NULL)
352
0
        ncdev->page_info.io_procs->fclose(ncdev->page_info.bfile, ncdev->page_info.bfname, false);
353
0
    if (ncdev->page_info.cfile != NULL)
354
0
        ncdev->page_info.io_procs->fclose(ncdev->page_info.cfile, ncdev->page_info.cfname, false);
355
0
    ncdev->do_not_open_or_close_bandfiles = true; /* we already closed the files */
356
357
    /* we can't get here with ndev == NULL */
358
0
    gdev_prn_free_memory(ndev);
359
0
    gs_free_object(thread_mem, ndev, "setup_device_and_mem_for_thread");
360
0
    gs_memory_chunk_release(thread_mem);
361
0
    return NULL;
362
0
}
363
364
/* Set up and start the render threads */
365
static int
366
clist_setup_render_threads(gx_device *dev, int y, gx_process_page_options_t *options)
367
0
{
368
0
    gx_device_printer *pdev = (gx_device_printer *)dev;
369
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
370
0
    gx_device_clist_common *cdev = (gx_device_clist_common *)cldev;
371
0
    gx_device_clist_reader *crdev = &cldev->reader;
372
0
    gs_memory_t *mem = cdev->bandlist_memory;
373
0
    gs_memory_t *chunk_base_mem = mem->thread_safe_memory;
374
0
    gs_memory_status_t mem_status;
375
0
    int i, j, band;
376
0
    int code = 0;
377
0
    int band_count = cdev->nbands;
378
0
    int band_height = crdev->page_info.band_params.BandHeight;
379
0
    byte **reserve_memory_array = NULL;
380
0
    int reserve_pdf14_memory_size = 0;
381
    /* space for the halftone cache plus 2Mb for other allocations during rendering (paths, etc.) */
382
    /* this will be increased by the measured profile storage and icclinks (estimated).     */
383
0
    int reserve_size = 2 * 1024 * 1024 + (gx_ht_cache_default_bits_size() * dev->color_info.num_components);
384
0
    clist_icctable_entry_t *curr_entry;
385
0
    bool deep = device_is_deep(dev);
386
387
0
    crdev->num_render_threads = pdev->num_render_threads_requested;
388
389
0
    if(gs_debug[':'] != 0)
390
0
        dmprintf1(mem, "%% %d rendering threads requested.\n", pdev->num_render_threads_requested);
391
392
0
    if (crdev->page_uses_transparency) {
393
0
        reserve_pdf14_memory_size = (ESTIMATED_PDF14_ROW_SPACE(max(1, crdev->width), crdev->color_info.num_components, deep ? 16 : 8) >> 3);
394
0
        reserve_pdf14_memory_size *= crdev->page_info.band_params.BandHeight; /* BandHeight set by writer */
395
0
    }
396
    /* scan the profile table sizes to get the total each thread will need */
397
0
    if (crdev->icc_table != NULL) {
398
0
        for (curr_entry = crdev->icc_table->head; curr_entry != NULL; curr_entry = curr_entry->next) {
399
0
            reserve_size += curr_entry->serial_data.size;
400
            /* FIXME: Should actually measure the icclink size to device (or pdf14 blend space) */
401
0
            reserve_size += 2 * 1024 * 1024;    /* a worst case estimate */
402
0
        }
403
0
    }
404
0
    if (crdev->num_render_threads > band_count)
405
0
        crdev->num_render_threads = band_count; /* don't bother starting more threads than bands */
406
    /* don't exceed our limit (allow for BGPrint and main thread) */
407
0
    if (crdev->num_render_threads > MAX_THREADS - 2)
408
0
        crdev->num_render_threads = MAX_THREADS - 2;
409
410
    /* Allocate and initialize an array of thread control structures */
411
0
    crdev->render_threads = (clist_render_thread_control_t *)
412
0
              gs_alloc_byte_array(mem, crdev->num_render_threads,
413
0
                                  sizeof(clist_render_thread_control_t),
414
0
                                  "clist_setup_render_threads");
415
    /* fallback to non-threaded if allocation fails */
416
0
    if (crdev->render_threads == NULL) {
417
0
        emprintf(mem, " VMerror prevented threads from starting.\n");
418
0
        return_error(gs_error_VMerror);
419
0
    }
420
0
    reserve_memory_array = (byte **)gs_alloc_byte_array(mem,
421
0
                                                        crdev->num_render_threads,
422
0
                                                        sizeof(void *),
423
0
                                                        "clist_setup_render_threads");
424
0
    if (reserve_memory_array == NULL) {
425
0
        gs_free_object(mem, crdev->render_threads, "clist_setup_render_threads");
426
0
        crdev->render_threads = NULL;
427
0
        emprintf(mem, " VMerror prevented threads from starting.\n");
428
0
        return_error(gs_error_VMerror);
429
0
    }
430
0
    memset(reserve_memory_array, 0, crdev->num_render_threads * sizeof(void *));
431
0
    memset(crdev->render_threads, 0, crdev->num_render_threads *
432
0
            sizeof(clist_render_thread_control_t));
433
434
0
    crdev->main_thread_data = cdev->data;               /* save data area */
435
    /* Based on the line number requested, decide the order of band rendering */
436
    /* Almost all devices go in increasing line order (except the bmp* devices ) */
437
0
    crdev->thread_lookahead_direction = (y < (cdev->height - 1)) ? 1 : -1;
438
0
    band = y / band_height;
439
440
    /* If the 'mem' is not thread safe, we need to wrap it in a locking memory */
441
0
    gs_memory_status(chunk_base_mem, &mem_status);
442
0
    if (mem_status.is_thread_safe == false) {
443
0
            return_error(gs_error_VMerror);
444
0
    }
445
446
    /* If we don't have one large enough already, create an icc cache list */
447
0
    if (crdev->num_render_threads > crdev->icc_cache_list_len) {
448
0
        gsicc_link_cache_t **old = crdev->icc_cache_list;
449
0
        crdev->icc_cache_list = (gsicc_link_cache_t **)gs_alloc_byte_array(mem->thread_safe_memory,
450
0
                                    crdev->num_render_threads,
451
0
                                    sizeof(void*), "clist_render_setup_threads");
452
0
        if (crdev->icc_cache_list == NULL) {
453
0
            crdev->icc_cache_list = NULL;
454
0
            return_error(gs_error_VMerror);
455
0
        }
456
0
        if (crdev->icc_cache_list_len > 0)
457
0
            memcpy(crdev->icc_cache_list, old, crdev->icc_cache_list_len * sizeof(gsicc_link_cache_t *));
458
0
        memset(&(crdev->icc_cache_list[crdev->icc_cache_list_len]), 0,
459
0
            (crdev->num_render_threads - crdev->icc_cache_list_len) * sizeof(void *));
460
0
        crdev->icc_cache_list_len = crdev->num_render_threads;
461
0
        gs_free_object(mem, old, "clist_render_setup_threads");
462
0
    }
463
464
    /* Loop creating the devices and semaphores for each thread, then start them */
465
0
    for (i=0; (i < crdev->num_render_threads) && (band >= 0) && (band < band_count);
466
0
            i++, band += crdev->thread_lookahead_direction) {
467
0
        gx_device *ndev;
468
0
        clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
469
470
        /* arbitrary extra reserve for other allocation by threads (paths, etc.) */
471
        /* plus the amount estimated for the pdf14 buffers */
472
0
        reserve_memory_array[i] = (byte *)gs_alloc_bytes(mem, reserve_size + reserve_pdf14_memory_size,
473
0
                                                         "clist_render_setup_threads");
474
0
        if (reserve_memory_array[i] == NULL) {
475
0
            code = gs_error_VMerror;  /* set code to an error for cleanup after the loop */
476
0
        break;
477
0
        }
478
0
        ndev = setup_device_and_mem_for_thread(chunk_base_mem, dev, false, &crdev->icc_cache_list[i]);
479
0
        if (ndev == NULL) {
480
0
            code = gs_error_VMerror;  /* set code to an error for cleanup after the loop */
481
0
            break;
482
0
        }
483
484
0
        thread->cdev = ndev;
485
0
        thread->memory = ndev->memory;
486
0
        thread->band = -1;              /* a value that won't match any valid band */
487
0
        thread->options = options;
488
0
        thread->buffer = NULL;
489
490
0
        {
491
0
            gx_device_clist *cldevl = (gx_device_clist *)ndev;
492
0
            gx_device_clist_reader *crdevl = &cldevl->reader;
493
494
            /* Both of these are only needed for the output_fn == NULL mode, but are initialized
495
             * regardless. */
496
0
            crdevl->curr_render_thread = i;
497
0
            crdevl->orig_clist_device = crdev;
498
0
        }
499
500
0
        if (options && options->init_buffer_fn) {
501
0
            code = options->init_buffer_fn(options->arg, dev, thread->memory, dev->width, band_height, &thread->buffer);
502
0
            if (code < 0)
503
0
                break;
504
0
        }
505
506
        /* create the buf device for this thread, and allocate the semaphores */
507
0
        if ((code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
508
0
                                &(thread->bdev), ndev,
509
0
                                band*crdev->page_info.band_params.BandHeight, NULL,
510
0
                                thread->memory, &(crdev->color_usage_array[0]))) < 0)
511
0
            break;
512
0
        if ((thread->sema_this = gx_semaphore_label(gx_semaphore_alloc(thread->memory), "Band")) == NULL ||
513
0
            (thread->sema_group = gx_semaphore_label(gx_semaphore_alloc(thread->memory), "Group")) == NULL) {
514
0
            code = gs_error_VMerror;
515
0
            break;
516
0
        }
517
        /* We don't start the threads yet until we  free up the */
518
        /* reserve memory we have allocated for that band. */
519
0
        thread->band = band;
520
0
    }
521
    /* If the code < 0, the last thread creation failed -- clean it up */
522
0
    if (code < 0) {
523
        /* NB: 'band' will be the one that failed, so will be the next_band needed to start */
524
        /* the following relies on 'free' ignoring NULL pointers */
525
0
        gx_semaphore_free(crdev->render_threads[i].sema_group);
526
0
        gx_semaphore_free(crdev->render_threads[i].sema_this);
527
0
        if (crdev->render_threads[i].bdev != NULL)
528
0
            cdev->buf_procs.destroy_buf_device(crdev->render_threads[i].bdev);
529
0
        if (crdev->render_threads[i].cdev != NULL) {
530
0
            gx_device_clist_common *thread_cdev = (gx_device_clist_common *)crdev->render_threads[i].cdev;
531
532
            /* Close the file handles, but don't delete (unlink) the files */
533
0
            thread_cdev->page_info.io_procs->fclose(thread_cdev->page_info.bfile, thread_cdev->page_info.bfname, false);
534
0
            thread_cdev->page_info.io_procs->fclose(thread_cdev->page_info.cfile, thread_cdev->page_info.cfname, false);
535
0
            thread_cdev->do_not_open_or_close_bandfiles = true; /* we already closed the files */
536
537
0
            gdev_prn_free_memory((gx_device *)thread_cdev);
538
0
            gs_free_object(crdev->render_threads[i].memory, thread_cdev,
539
0
            "clist_setup_render_threads");
540
0
        }
541
0
        if (crdev->render_threads[i].buffer != NULL && options && options->free_buffer_fn != NULL) {
542
0
            options->free_buffer_fn(options->arg, dev, crdev->render_threads[i].memory, crdev->render_threads[i].buffer);
543
0
            crdev->render_threads[i].buffer = NULL;
544
0
        }
545
0
        if (crdev->render_threads[i].memory != NULL) {
546
0
            gs_memory_chunk_release(crdev->render_threads[i].memory);
547
0
            crdev->render_threads[i].memory = NULL;
548
0
        }
549
0
    }
550
    /* If we weren't able to create at least one thread, punt   */
551
    /* Although a single thread isn't any more efficient, the   */
552
    /* machinery still works, so that's OK.                     */
553
0
    if (i == 0) {
554
0
        if (crdev->render_threads[0].memory != NULL) {
555
0
            gs_memory_chunk_release(crdev->render_threads[0].memory);
556
0
            if (chunk_base_mem != mem) {
557
0
                gs_free_object(mem, chunk_base_mem, "clist_setup_render_threads(locked allocator)");
558
0
            }
559
0
        }
560
0
        gs_free_object(mem, crdev->render_threads, "clist_setup_render_threads");
561
0
        crdev->render_threads = NULL;
562
        /* restore the file pointers */
563
0
        if (cdev->page_info.cfile == NULL) {
564
0
            char fmode[4];
565
566
0
            strcpy(fmode, "a+");        /* file already exists and we want to re-use it */
567
0
            strncat(fmode, gp_fmode_binary_suffix, 1);
568
0
            cdev->page_info.io_procs->fopen(cdev->page_info.cfname, fmode, &cdev->page_info.cfile,
569
0
                                mem, cdev->bandlist_memory, true);
570
0
            cdev->page_info.io_procs->fseek(cdev->page_info.cfile, 0, SEEK_SET, cdev->page_info.cfname);
571
0
            cdev->page_info.io_procs->fopen(cdev->page_info.bfname, fmode, &cdev->page_info.bfile,
572
0
                                mem, cdev->bandlist_memory, false);
573
0
            cdev->page_info.io_procs->fseek(cdev->page_info.bfile, 0, SEEK_SET, cdev->page_info.bfname);
574
0
        }
575
0
        emprintf1(mem, "Rendering threads not started, code=%d.\n", code);
576
0
        return_error(code);
577
0
    }
578
    /* Free up any "reserve" memory we may have allocated, and start the
579
     * threads since we deferred that in the thread setup loop above.
580
     * We know if we get here we can start at least 1 thread.
581
     */
582
0
    for (j=0, code = 0; j<crdev->num_render_threads; j++) {
583
0
        gs_free_object(mem, reserve_memory_array[j], "clist_setup_render_threads");
584
0
        if (code == 0 && j < i)
585
0
            code = clist_start_render_thread(dev, j, crdev->render_threads[j].band);
586
0
    }
587
0
    gs_free_object(mem, reserve_memory_array, "clist_setup_render_threads");
588
0
    crdev->num_render_threads = i;
589
0
    crdev->curr_render_thread = 0;
590
0
    crdev->next_band = band;
591
592
0
    if(gs_debug[':'] != 0)
593
0
        dmprintf1(mem, "%% Using %d rendering threads\n", i);
594
595
0
    return code;
596
0
}
597
598
/* This is also exported for teardown after background printing */
599
void
600
teardown_device_and_mem_for_thread(gx_device *dev, gp_thread_id thread_id, bool bg_print)
601
0
{
602
0
    gx_device_clist_common *thread_cdev = (gx_device_clist_common *)dev;
603
0
    gx_device_clist_reader *thread_crdev = (gx_device_clist_reader *)dev;
604
0
    gs_memory_t *thread_memory = dev->memory;
605
606
    /* First finish the thread */
607
0
    gp_thread_finish(thread_id);
608
609
0
    if (bg_print) {
610
        /* we are cleaning up a background printing thread, so we clean up similarly to */
611
        /* what is done  by clist_finish_page, but without re-opening the clist files.  */
612
0
        clist_teardown_render_threads(dev); /* we may have used multiple threads */
613
        /* free the thread's icc_table since this was not done by clist_finish_page */
614
0
        clist_free_icc_table(thread_crdev->icc_table, thread_memory);
615
0
        thread_crdev->icc_table = NULL;
616
        /* NB: gdev_prn_free_memory below will free the color_usage_array */
617
0
    } else {
618
        /* make sure this doesn't get freed by gdev_prn_free_memory below */
619
0
        ((gx_device_clist_reader *)thread_cdev)->color_usage_array = NULL;
620
621
        /* For non-bg_print cases the icc_table is shared between devices, but
622
         * is not reference counted or anything. We rely on it being shared with
623
         * and owned by the "parent" device in the interpreter thread, hence
624
         * null it here to avoid it being freed as we cleanup the thread device.
625
         */
626
0
        thread_crdev->icc_table = NULL;
627
0
    }
628
0
    rc_decrement(thread_crdev->icc_cache_cl, "teardown_render_thread");
629
0
    thread_crdev->icc_cache_cl = NULL;
630
    /*
631
     * Free the BufferSpace, close the band files, optionally unlinking them.
632
     * We unlink the files if this call is cleaning up from bg printing.
633
     * Note that the BufferSpace is freed using 'ppdev->buf' so the 'data'
634
     * pointer doesn't need to be the one that the thread started with
635
     */
636
    /* If this thread was being used for background printing and NumRenderingThreads > 0 */
637
    /* the clist_setup_render_threads may have already closed these files                */
638
    /* Note that in the case of back ground printing, we only want to close the instance  */
639
    /* of the files for the reader (hence the final parameter being false). We'll clean  */
640
    /* the original instance of the files in prn_finish_bg_print()                       */
641
0
    if (thread_cdev->page_info.bfile != NULL)
642
0
        thread_cdev->page_info.io_procs->fclose(thread_cdev->page_info.bfile, thread_cdev->page_info.bfname, false);
643
0
    if (thread_cdev->page_info.cfile != NULL)
644
0
        thread_cdev->page_info.io_procs->fclose(thread_cdev->page_info.cfile, thread_cdev->page_info.cfname, false);
645
0
    thread_cdev->page_info.bfile = thread_cdev->page_info.cfile = NULL;
646
0
    thread_cdev->do_not_open_or_close_bandfiles = true; /* we already closed the files */
647
648
0
    gdev_prn_free_memory((gx_device *)thread_cdev);
649
    /* Free the device copy this thread used.  Note that the
650
       deviceN stuff if was allocated and copied earlier for the device
651
       will be freed with this call and the icc_struct ref count will be decremented. */
652
0
    gs_free_object(thread_memory, thread_cdev, "clist_teardown_render_threads");
653
#ifdef DEBUG
654
    dmprintf(thread_memory, "rendering thread ending memory state...\n");
655
    gs_memory_chunk_dump_memory(thread_memory);
656
    dmprintf(thread_memory, "                                    memory dump done.\n");
657
#endif
658
0
    gs_memory_chunk_release(thread_memory);
659
0
}
660
661
void
662
clist_teardown_render_threads(gx_device *dev)
663
771k
{
664
771k
    gx_device_clist *cldev = (gx_device_clist *)dev;
665
771k
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
666
771k
    gx_device_clist_reader *crdev = &cldev->reader;
667
771k
    gs_memory_t *mem = cdev->bandlist_memory;
668
771k
    int i;
669
670
771k
    if (crdev->render_threads != NULL) {
671
        /* Wait for all threads to finish */
672
0
        for (i = (crdev->num_render_threads - 1); i >= 0; i--) {
673
0
            clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
674
675
0
            if (thread->status == THREAD_BUSY)
676
0
                gx_semaphore_wait(thread->sema_this);
677
0
        }
678
        /* then free each thread's memory */
679
0
        for (i = (crdev->num_render_threads - 1); i >= 0; i--) {
680
0
            clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
681
0
            gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;
682
683
            /* Free control semaphores */
684
0
            gx_semaphore_free(thread->sema_group);
685
0
            gx_semaphore_free(thread->sema_this);
686
            /* destroy the thread's buffer device */
687
0
            thread_cdev->buf_procs.destroy_buf_device(thread->bdev);
688
689
0
            if (thread->options) {
690
0
                if (thread->options->free_buffer_fn && thread->buffer) {
691
0
                    thread->options->free_buffer_fn(thread->options->arg, dev, thread->memory, thread->buffer);
692
0
                    thread->buffer = NULL;
693
0
                }
694
0
                thread->options = NULL;
695
0
            }
696
697
            /* before freeing this device's memory, swap with cdev if it was the main_thread_data */
698
0
            if (thread_cdev->data == crdev->main_thread_data) {
699
0
                thread_cdev->data = cdev->data;
700
0
                cdev->data = crdev->main_thread_data;
701
0
            }
702
#ifdef DEBUG
703
            if (gs_debug[':'])
704
                dmprintf2(thread->memory, "%% Thread %d total usertime=%ld msec\n", i, thread->cputime);
705
            dmprintf1(thread->memory, "\nThread %d ", i);
706
#endif
707
0
            teardown_device_and_mem_for_thread((gx_device *)thread_cdev, thread->thread, false);
708
0
        }
709
0
        gs_free_object(mem, crdev->render_threads, "clist_teardown_render_threads");
710
0
        crdev->render_threads = NULL;
711
712
        /* Now re-open the clist temp files so we can write to them */
713
0
        if (cdev->page_info.cfile == NULL) {
714
0
            char fmode[4];
715
716
0
            strcpy(fmode, "a+");        /* file already exists and we want to re-use it */
717
0
            strncat(fmode, gp_fmode_binary_suffix, 1);
718
0
            cdev->page_info.io_procs->fopen(cdev->page_info.cfname, fmode, &cdev->page_info.cfile,
719
0
                                mem, cdev->bandlist_memory, true);
720
0
            cdev->page_info.io_procs->fseek(cdev->page_info.cfile, 0, SEEK_SET, cdev->page_info.cfname);
721
0
            cdev->page_info.io_procs->fopen(cdev->page_info.bfname, fmode, &cdev->page_info.bfile,
722
0
                                mem, cdev->bandlist_memory, false);
723
0
            cdev->page_info.io_procs->fseek(cdev->page_info.bfile, 0, SEEK_SET, cdev->page_info.bfname);
724
0
        }
725
0
    }
726
771k
}
727
728
static int
729
clist_start_render_thread(gx_device *dev, int thread_index, int band)
730
0
{
731
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
732
0
    gx_device_clist_reader *crdev = &cldev->reader;
733
0
    int code;
734
0
    gp_thread_creation_callback_t starter;
735
0
    gx_process_page_options_t* options = crdev->render_threads[thread_index].options;
736
737
0
    crdev->render_threads[thread_index].band = band;
738
739
    /* Finally, fire it up */
740
0
    if (options == NULL || options->output_fn) {
741
        /* Traditional mechanism, using output_fn. Each thread will
742
         * block after it renders until it has been output 'in-order'
743
         * using output_fn. */
744
0
        crdev->render_threads[thread_index].status = THREAD_BUSY;
745
746
0
        starter = clist_render_thread;
747
0
    } else {
748
        /* New mechanism, with output_fn == NULL. process_fn is
749
         * required to both render and output the data. Less blocking
750
         * required, and potentially significant speedups as long as
751
         * output does not need to be 'in-order'. */
752
0
        starter = clist_render_thread_no_output_fn;
753
754
        /* This could pretty much be an assert. */
755
0
        if (options == NULL || options->process_fn == NULL)
756
0
            return_error(gs_error_rangecheck);
757
0
    }
758
0
    code = gp_thread_start(starter,
759
0
                           &(crdev->render_threads[thread_index]),
760
0
                           &(crdev->render_threads[thread_index].thread));
761
762
0
    gp_thread_label(crdev->render_threads[thread_index].thread, "Band");
763
764
0
    return code;
765
0
}
766
767
static void
768
clist_render_thread(void *data)
769
0
{
770
0
    clist_render_thread_control_t *thread = (clist_render_thread_control_t *)data;
771
0
    gx_device *dev = thread->cdev;
772
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
773
0
    gx_device_clist_reader *crdev = &cldev->reader;
774
0
    gx_device *bdev = thread->bdev;
775
0
    gs_int_rect band_rect;
776
0
    byte *mdata = crdev->data + crdev->page_info.tile_cache_size;
777
0
    byte *mlines = (crdev->page_info.line_ptrs_offset == 0 ? NULL : mdata + crdev->page_info.line_ptrs_offset);
778
0
    uint raster = gx_device_raster_plane(dev, NULL);
779
0
    int code;
780
0
    int band_height = crdev->page_info.band_params.BandHeight;
781
0
    int band = thread->band;
782
0
    int band_begin_line = band * band_height;
783
0
    int band_end_line = band_begin_line + band_height;
784
0
    int band_num_lines;
785
#ifdef DEBUG
786
    long starttime[2], endtime[2];
787
788
    gp_get_usertime(starttime); /* thread start time */
789
#endif
790
0
    if (band_end_line > dev->height)
791
0
        band_end_line = dev->height;
792
0
    band_num_lines = band_end_line - band_begin_line;
793
794
0
    code = crdev->buf_procs.setup_buf_device
795
0
            (bdev, mdata, raster, (byte **)mlines, 0, band_num_lines, band_num_lines);
796
0
    band_rect.p.x = 0;
797
0
    band_rect.p.y = band_begin_line;
798
0
    band_rect.q.x = dev->width;
799
0
    band_rect.q.y = band_end_line;
800
0
    if (code >= 0)
801
0
        code = clist_render_rectangle(cldev, &band_rect, bdev, NULL, true);
802
803
0
    if (code >= 0 && thread->options && thread->options->process_fn)
804
0
        code = thread->options->process_fn(thread->options->arg, dev, bdev, &band_rect, thread->buffer);
805
806
    /* Reset the band boundaries now */
807
0
    crdev->ymin = band_begin_line;
808
0
    crdev->ymax = band_end_line;
809
0
    crdev->offset_map = NULL;
810
0
    if (code < 0)
811
0
        thread->status = THREAD_ERROR;          /* shouldn't happen */
812
0
    else
813
0
        thread->status = THREAD_DONE;    /* OK */
814
815
#ifdef DEBUG
816
    gp_get_usertime(endtime);
817
    thread->cputime += (endtime[0] - starttime[0]) * 1000 +
818
             (endtime[1] - starttime[1]) / 1000000;
819
#endif
820
    /*
821
     * Signal the semaphores. We signal the 'group' first since even if
822
     * the waiter is released on the group, it still needs to check
823
     * status on the thread
824
     */
825
0
    gx_semaphore_signal(thread->sema_group);
826
0
    gx_semaphore_signal(thread->sema_this);
827
0
}
828
829
/* Used if output_fn == NULL. No blocking required as we no longer need to
830
 * serialise the calls to output_fn. */
831
static void
832
clist_render_thread_no_output_fn(void* data)
833
0
{
834
0
    clist_render_thread_control_t* thread = (clist_render_thread_control_t*)data;
835
0
    gx_device* dev = thread->cdev;
836
0
    gx_device_clist* cldev = (gx_device_clist*)dev;
837
0
    gx_device_clist_reader* crdev = &cldev->reader;
838
0
    gx_device* bdev = thread->bdev;
839
0
    gs_int_rect band_rect;
840
0
    byte* mdata = crdev->data + crdev->page_info.tile_cache_size;
841
0
    byte* mlines = (crdev->page_info.line_ptrs_offset == 0 ? NULL : mdata + crdev->page_info.line_ptrs_offset);
842
0
    uint raster = gx_device_raster_plane(dev, NULL);
843
0
    int code;
844
0
    int band_height = crdev->page_info.band_params.BandHeight;
845
0
    int band_begin_line = thread->band * band_height;
846
0
    int band_end_line = band_begin_line + band_height;
847
0
    int band_num_lines;
848
849
#ifdef DEBUG
850
    long starttime[2], endtime[2];
851
852
    gp_get_usertime(starttime); /* thread start time */
853
#endif
854
    /*
855
      As long there is no need for calling the bands 'in order' we can
856
      process multiple bands with one thread while there are free
857
      (unprocessed) bands available. The only potential race condition
858
      here is where we need to pick a new band to draw when a thread
859
      finishes its render task. We delegate the job of doing this to the
860
      process_fn itself, enabling devices to make use of less expensive
861
      operations (such as fast interlocked exchange with add) that may
862
      not be available portably.
863
      If there is no more unprocessed band the thread will finish, giving
864
      back the control to the main process (obviously after ALL the
865
      rendering threads have got finished).
866
    */
867
868
0
    while (band_begin_line < dev->height && band_end_line > 0) {
869
0
        if (band_end_line > dev->height)
870
0
            band_end_line = dev->height;
871
0
        band_num_lines = band_end_line - band_begin_line;
872
873
0
        ((gx_device_memory*)bdev)->band_y = band_begin_line; /* probably useless, but doesn't hurt */
874
875
0
        code = crdev->buf_procs.setup_buf_device
876
0
        (bdev, mdata, raster, (byte**)mlines, 0, band_num_lines, band_num_lines);
877
878
0
        band_rect.p.x = 0;
879
0
        band_rect.p.y = band_begin_line;
880
0
        band_rect.q.x = dev->width;
881
0
        band_rect.q.y = band_end_line;
882
883
0
        if (code >= 0)
884
0
            code = clist_render_rectangle(cldev, &band_rect, bdev, NULL, true);
885
886
0
        if (code >= 0)
887
0
            code = thread->options->process_fn(thread->options->arg, dev, bdev, &band_rect, thread->buffer);
888
889
        /* A side effect of the process_fn is that it should select the next
890
          * band for this thread to render. It does this by updating
891
          * crdev->next_band (that's the thread's own device!), as well as
892
          * updating crdev_orig so that other threads won't try to render the
893
          * same thread (normally by updating crdev_orig->next_band). */
894
0
        band_begin_line = crdev->next_band * band_height;
895
0
        band_end_line = band_begin_line + band_height;
896
0
        if (code < 0)
897
0
            break;
898
0
    }
899
900
901
#ifdef DEBUG
902
    gp_get_usertime(endtime);
903
    thread->cputime += (endtime[0] - starttime[0]) * 1000 +
904
        (endtime[1] - starttime[1]) / 1000000;
905
#endif
906
0
    if (code < 0)
907
0
        thread->status = THREAD_ERROR;          /* shouldn't happen */
908
0
    else
909
0
        thread->status = THREAD_DONE;    /* OK */
910
0
}
911
912
/*
913
 * Copy the raster data from the completed thread to the caller's
914
 * device (the main thread)
915
 * Return 0 if OK, < 0 is the error code from the thread
916
 *
917
 * After swapping the pointers, start up the completed thread with the
918
 * next band remaining to do (if any)
919
 */
920
static int
921
clist_get_band_from_thread(gx_device *dev, int band_needed, gx_process_page_options_t *options)
922
0
{
923
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
924
0
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
925
0
    gx_device_clist_reader *crdev = &cldev->reader;
926
0
    int i, code = 0;
927
0
    int thread_index = crdev->curr_render_thread;
928
0
    clist_render_thread_control_t *thread = &(crdev->render_threads[thread_index]);
929
0
    gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;
930
0
    int band_height = crdev->page_info.band_params.BandHeight;
931
0
    int band_count = cdev->nbands;
932
0
    byte *tmp;                  /* for swapping data areas */
933
934
    /* We expect that the thread needed will be the 'current' thread */
935
0
    if (thread->band != band_needed) {
936
0
        int band = band_needed;
937
938
0
        emprintf3(thread->memory,
939
0
                  "thread->band = %d, band_needed = %d, direction = %d, ",
940
0
                  thread->band, band_needed, crdev->thread_lookahead_direction);
941
942
        /* Probably we went in the wrong direction, so let the threads */
943
        /* all complete, then restart them in the opposite direction   */
944
        /* If the caller is 'bouncing around' we may end up back here, */
945
        /* but that is a VERY rare case (we haven't seen it yet).      */
946
0
        for (i=0; i < crdev->num_render_threads; i++) {
947
0
            clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
948
949
0
            if (thread->status == THREAD_BUSY)
950
0
                gx_semaphore_wait(thread->sema_this);
951
0
        }
952
0
        crdev->thread_lookahead_direction *= -1;      /* reverse direction (but may be overruled below) */
953
0
        if (band_needed == band_count-1)
954
0
            crdev->thread_lookahead_direction = -1;   /* assume backwards if we are asking for the last band */
955
0
        if (band_needed == 0)
956
0
            crdev->thread_lookahead_direction = 1;    /* force forward if we are looking for band 0 */
957
958
0
        dmprintf1(thread->memory, "new_direction = %d\n", crdev->thread_lookahead_direction);
959
960
        /* Loop starting the threads in the new lookahead_direction */
961
0
        for (i=0; (i < crdev->num_render_threads) && (band >= 0) && (band < band_count);
962
0
                i++, band += crdev->thread_lookahead_direction) {
963
0
            thread = &(crdev->render_threads[i]);
964
0
            thread->band = -1;          /* a value that won't match any valid band */
965
            /* Start thread 'i' to do band */
966
0
            if ((code = clist_start_render_thread(dev, i, band)) < 0)
967
0
                break;
968
0
        }
969
0
        crdev->next_band = i;     /* may be < 0 or == band_count, but that is handled later */
970
0
        crdev->curr_render_thread = thread_index = 0;
971
0
        thread = &(crdev->render_threads[0]);
972
0
        thread_cdev = (gx_device_clist_common *)thread->cdev;
973
0
    }
974
    /* Wait for this thread */
975
0
    gx_semaphore_wait(thread->sema_this);
976
0
    gp_thread_finish(thread->thread);
977
0
    thread->thread = NULL;
978
0
    if (thread->status == THREAD_ERROR)
979
0
        return_error(gs_error_unknownerror);          /* FAIL */
980
981
0
    if (options && options->output_fn) {
982
0
        code = options->output_fn(options->arg, dev, thread->buffer);
983
0
        if (code < 0)
984
0
            return code;
985
0
    }
986
987
    /* Swap the data areas to avoid the copy */
988
0
    tmp = cdev->data;
989
0
    cdev->data = thread_cdev->data;
990
0
    thread_cdev->data = tmp;
991
0
    thread->status = THREAD_IDLE;        /* the data is no longer valid */
992
0
    thread->band = -1;
993
    /* Update the bounds for this band */
994
0
    cdev->ymin =  band_needed * band_height;
995
0
    cdev->ymax =  cdev->ymin + band_height;
996
0
    if (cdev->ymax > dev->height)
997
0
        cdev->ymax = dev->height;
998
999
0
    if (crdev->next_band >= 0 && crdev->next_band < band_count) {
1000
0
        code = clist_start_render_thread(dev, thread_index, crdev->next_band);
1001
0
        crdev->next_band += crdev->thread_lookahead_direction;
1002
0
    }
1003
    /* bump the 'curr' to the next thread */
1004
0
    crdev->curr_render_thread = crdev->curr_render_thread == crdev->num_render_threads - 1 ?
1005
0
                0 : crdev->curr_render_thread + 1;
1006
1007
0
    return code;
1008
0
}
1009
1010
/* Copy a rasterized rectangle to the client, rasterizing if needed. */
1011
/* The first invocation starts multiple threads to perform "look ahead" */
1012
/* rendering adjacent to the first band (forward or backward) */
1013
static int
1014
clist_get_bits_rect_mt(gx_device *dev, const gs_int_rect * prect,
1015
                         gs_get_bits_params_t *params)
1016
0
{
1017
0
    gx_device_printer *pdev = (gx_device_printer *)dev;
1018
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
1019
0
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
1020
0
    gx_device_clist_reader *crdev = &cldev->reader;
1021
0
    gs_memory_t *mem = cdev->bandlist_memory;
1022
0
    gs_get_bits_options_t options = params->options;
1023
0
    int y = prect->p.y;
1024
0
    int end_y = prect->q.y;
1025
0
    int line_count = end_y - y;
1026
0
    int band_height = crdev->page_info.band_params.BandHeight;
1027
0
    int band = y / band_height;
1028
0
    gs_int_rect band_rect;
1029
0
    int lines_rasterized;
1030
0
    gx_device *bdev;
1031
0
    byte *mdata;
1032
0
    uint raster = gx_device_raster(dev, 1);
1033
0
    int my;
1034
0
    int code = 0;
1035
1036
    /* This page might not want multiple threads */
1037
    /* Also we don't support plane extraction using multiple threads */
1038
0
    if (pdev->num_render_threads_requested < 1 || (options & GB_SELECT_PLANES))
1039
0
        return clist_get_bits_rectangle(dev, prect, params);
1040
1041
0
    if (prect->p.x < 0 || prect->q.x > dev->width ||
1042
0
        y < 0 || end_y > dev->height
1043
0
        )
1044
0
        return_error(gs_error_rangecheck);
1045
0
    if (line_count <= 0 || prect->p.x >= prect->q.x)
1046
0
        return 0;
1047
1048
0
    if (crdev->ymin < 0)
1049
0
        if ((code = clist_close_writer_and_init_reader(cldev)) < 0)
1050
0
            return code; /* can't recover from this */
1051
1052
0
    if (crdev->ymin == 0 && crdev->ymax == 0 && crdev->render_threads == NULL) {
1053
        /* Haven't done any rendering yet, try to set up the threads */
1054
0
        if (clist_setup_render_threads(dev, y, NULL) < 0)
1055
            /* problem setting up the threads, revert to single threaded */
1056
0
            return clist_get_bits_rectangle(dev, prect, params);
1057
0
    } else {
1058
0
        if (crdev->render_threads == NULL) {
1059
            /* If we get here with with ymin and ymax > 0 it's because we closed the threads */
1060
            /* while doing a page due to an error. Use single threaded mode.         */
1061
0
            return clist_get_bits_rectangle(dev, prect, params);
1062
0
        }
1063
0
    }
1064
    /* If we already have the band's data, just return it */
1065
0
    if (y < crdev->ymin || end_y > crdev->ymax)
1066
0
        code = clist_get_band_from_thread(dev, band, NULL);
1067
0
    if (code < 0)
1068
0
        goto free_thread_out;
1069
0
    mdata = crdev->data + crdev->page_info.tile_cache_size;
1070
0
    if ((code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
1071
0
                                  &bdev, cdev->target, y, NULL,
1072
0
                                  mem, &(crdev->color_usage_array[band]))) < 0 ||
1073
0
        (code = crdev->buf_procs.setup_buf_device(bdev, mdata, raster, NULL,
1074
0
                            y - crdev->ymin, line_count, crdev->ymax - crdev->ymin)) < 0)
1075
0
        goto free_thread_out;
1076
1077
0
    lines_rasterized = min(band_height, line_count);
1078
    /* Return as much of the rectangle as falls within the rasterized lines. */
1079
0
    band_rect = *prect;
1080
0
    band_rect.p.y = 0;
1081
0
    band_rect.q.y = lines_rasterized;
1082
0
    code = dev_proc(bdev, get_bits_rectangle)
1083
0
        (bdev, &band_rect, params);
1084
0
    cdev->buf_procs.destroy_buf_device(bdev);
1085
0
    if (code < 0)
1086
0
        goto free_thread_out;
1087
1088
    /* Note that if called via 'get_bits', the line count will always be 1 */
1089
0
    if (lines_rasterized == line_count) {
1090
0
        return code;
1091
0
    }
1092
1093
/***** TODO: Handle the below with data from the threads *****/
1094
    /*
1095
     * We'll have to return the rectangle in pieces.  Force GB_RETURN_COPY
1096
     * rather than GB_RETURN_POINTER, and require all subsequent pieces to
1097
     * use the same values as the first piece for all of the other format
1098
     * options.  If copying isn't allowed, or if there are any unread
1099
     * rectangles, punt.
1100
     */
1101
0
    if (!(options & GB_RETURN_COPY) || code > 0)
1102
0
        return gx_default_get_bits_rectangle(dev, prect, params);
1103
0
    options = params->options;
1104
0
    if (!(options & GB_RETURN_COPY)) {
1105
        /* Redo the first piece with copying. */
1106
0
        params->options = (params->options & ~GB_RETURN_ALL) | GB_RETURN_COPY;
1107
0
        lines_rasterized = 0;
1108
0
    }
1109
0
    {
1110
0
        gs_get_bits_params_t band_params;
1111
0
        uint raster = gx_device_raster(bdev, true);
1112
1113
0
        code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
1114
0
                                      &bdev, cdev->target, y, NULL,
1115
0
                                      mem, &(crdev->color_usage_array[band]));
1116
0
        if (code < 0)
1117
0
            return code;
1118
0
        band_params = *params;
1119
0
        while ((y += lines_rasterized) < end_y) {
1120
            /* Increment data pointer by lines_rasterized. */
1121
0
            band_params.data[0] += raster * lines_rasterized;
1122
0
            line_count = end_y - y;
1123
0
            code = clist_rasterize_lines(dev, y, line_count, bdev, NULL, &my);
1124
0
            if (code < 0)
1125
0
                break;
1126
0
            lines_rasterized = min(code, line_count);
1127
0
            band_rect.p.y = my;
1128
0
            band_rect.q.y = my + lines_rasterized;
1129
0
            code = dev_proc(bdev, get_bits_rectangle)
1130
0
                (bdev, &band_rect, &band_params);
1131
0
            if (code < 0)
1132
0
                break;
1133
0
            params->options = band_params.options;
1134
0
            if (lines_rasterized == line_count)
1135
0
                break;
1136
0
        }
1137
0
        cdev->buf_procs.destroy_buf_device(bdev);
1138
0
    }
1139
0
    return code;
1140
1141
/* Free up thread stuff */
1142
0
free_thread_out:
1143
0
    clist_teardown_render_threads(dev);
1144
0
    return code;
1145
0
}
1146
1147
int
1148
clist_process_page(gx_device *dev, gx_process_page_options_t *options)
1149
0
{
1150
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
1151
0
    gx_device_clist_reader *crdev = &cldev->reader;
1152
0
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
1153
0
    int y;
1154
0
    int line_count;
1155
0
    int band_height = crdev->page_info.band_params.BandHeight;
1156
0
    gs_int_rect band_rect;
1157
0
    int lines_rasterized;
1158
0
    gx_device *bdev;
1159
0
    gx_render_plane_t render_plane;
1160
0
    int my;
1161
0
    int code;
1162
0
    void *buffer = NULL;
1163
1164
0
    if (0 > (code = clist_close_writer_and_init_reader(cldev)))
1165
0
        return code;
1166
1167
0
    if (options->init_buffer_fn) {
1168
0
        code = options->init_buffer_fn(options->arg, dev, dev->memory, dev->width, band_height, &buffer);
1169
0
        if (code < 0)
1170
0
            return code;
1171
0
    }
1172
1173
0
    gx_render_plane_init(&render_plane, dev, -1);
1174
0
    for (y = 0; y < dev->height; y += lines_rasterized)
1175
0
    {
1176
0
        line_count = band_height;
1177
0
        if (line_count > dev->height - y)
1178
0
            line_count = dev->height - y;
1179
0
        code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
1180
0
                                      &bdev, cdev->target, y, &render_plane,
1181
0
                                      dev->memory,
1182
0
                                      &(crdev->color_usage_array[y/band_height]));
1183
0
        if (code < 0)
1184
0
            return code;
1185
0
        code = clist_rasterize_lines(dev, y, line_count, bdev, &render_plane, &my);
1186
0
        if (code >= 0)
1187
0
        {
1188
0
            lines_rasterized = min(code, line_count);
1189
1190
            /* Return as much of the rectangle as falls within the rasterized lines. */
1191
0
            band_rect.p.x = 0;
1192
0
            band_rect.p.y = y;
1193
0
            band_rect.q.x = dev->width;
1194
0
            band_rect.q.y = y + lines_rasterized;
1195
0
            if (options->process_fn)
1196
0
                code = options->process_fn(options->arg, dev, bdev, &band_rect, buffer);
1197
0
        }
1198
0
        if (code >= 0 && options->output_fn)
1199
0
            code = options->output_fn(options->arg, dev, buffer);
1200
0
        cdev->buf_procs.destroy_buf_device(bdev);
1201
0
        if (code < 0)
1202
0
            break;
1203
0
    }
1204
1205
0
    if (options->free_buffer_fn) {
1206
0
        options->free_buffer_fn(options->arg, dev, dev->memory, buffer);
1207
0
    }
1208
1209
0
    return code;
1210
0
}
1211
1212
static int
1213
clist_process_page_mt(gx_device *dev, gx_process_page_options_t *options)
1214
0
{
1215
0
    gx_device_printer *pdev = (gx_device_printer *)dev;
1216
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
1217
0
    gx_device_clist_reader *crdev = &cldev->reader;
1218
0
    int band_height = crdev->page_info.band_params.BandHeight;
1219
0
    int band;
1220
0
    int num_bands = (dev->height + band_height-1)/band_height;
1221
0
    int code;
1222
0
    int reverse = !!(options->options & GX_PROCPAGE_BOTTOM_UP);
1223
1224
    /* This page might not want multiple threads */
1225
    /* Also we don't support plane extraction using multiple threads */
1226
0
    if (pdev->num_render_threads_requested < 1)
1227
0
        return clist_process_page(dev, options);
1228
1229
0
    if ((code = clist_close_writer_and_init_reader(cldev)) < 0)
1230
0
        return code; /* can't recover from this */
1231
1232
    /* Haven't done any rendering yet, try to set up the threads */
1233
0
    if (clist_setup_render_threads(dev, reverse ? dev->height-1 : 0, options) < 0)
1234
        /* problem setting up the threads, revert to single threaded */
1235
0
        return clist_process_page(dev, options);
1236
1237
0
    if (options->output_fn) {
1238
        /* Traditional mechanism: The rendering threads are running. We wait for them
1239
         * to finish in order, call output_fn with the results, and kick off the
1240
         * next bands rendering. This means that threads block after they finish
1241
         * rendering until it is their turn to call output_fn. */
1242
0
        if (reverse)
1243
0
        {
1244
0
            for (band = num_bands - 1; band > 0; band--)
1245
0
            {
1246
0
                code = clist_get_band_from_thread(dev, band, options);
1247
0
                if (code < 0)
1248
0
                    goto free_thread_out;
1249
0
            }
1250
0
        }
1251
0
        else
1252
0
        {
1253
0
            for (band = 0; band < num_bands; band++)
1254
0
            {
1255
0
                code = clist_get_band_from_thread(dev, band, options);
1256
0
                if (code < 0)
1257
0
                    goto free_thread_out;
1258
0
            }
1259
0
        }
1260
0
    }
1261
0
    else
1262
0
    {
1263
        /* New mechanism: The rendering threads are running. Each of them will
1264
         * automatically loop; rendering the next band necessary, outputting the
1265
         * results (directly, as there is no output_fn to call), and selecting
1266
         * the next band to operate on. The threads will exit once there are no
1267
         * more bands to render/output. All we need do here, therefore, is wait
1268
         * for them to exit. */
1269
0
        int i;
1270
0
        int failed = 0;
1271
0
        for (i = 0; i < crdev->num_render_threads; i++) {
1272
0
            gp_thread_finish(crdev->render_threads[i].thread);
1273
0
            if (crdev->render_threads[i].status == THREAD_ERROR)
1274
0
                failed = 1;
1275
0
            crdev->render_threads[i].thread = 0;
1276
0
        }
1277
0
        if (failed)
1278
0
            code = gs_note_error(gs_error_unknownerror);
1279
0
    }
1280
1281
    /* Always free up thread stuff before exiting */
1282
0
free_thread_out:
1283
0
    clist_teardown_render_threads(dev);
1284
0
    return code;
1285
0
}
1286
1287
static void
1288
test_threads(void *dummy)
1289
0
{
1290
0
}
1291
1292
int
1293
clist_enable_multi_thread_render(gx_device *dev)
1294
0
{
1295
0
    int code = -1;
1296
0
    gp_thread_id thread;
1297
1298
0
    if (dev->procs.get_bits_rectangle == clist_get_bits_rect_mt)
1299
0
        return 1; /* no need to test again */
1300
    /* We need to test gp_thread_start since we may be on a platform  */
1301
    /* built without working threads, i.e., using gp_nsync.c dummy    */
1302
    /* routines. The nosync gp_thread_start returns a -ve error code. */
1303
0
    if ((code = gp_thread_start(test_threads, NULL, &thread)) < 0 ) {
1304
0
        return code;    /* Threads don't work */
1305
0
    }
1306
0
    gp_thread_label(thread, "test_thread");
1307
0
    gp_thread_finish(thread);
1308
0
    set_dev_proc(dev, get_bits_rectangle, clist_get_bits_rect_mt);
1309
0
    set_dev_proc(dev, process_page, clist_process_page_mt);
1310
1311
0
    return 1;
1312
0
}