Coverage Report

Created: 2026-07-24 07:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ghostpdl/base/gxclthrd.c
Line
Count
Source
1
/* Copyright (C) 2001-2026 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, k, 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
0
    k = crdev->num_render_threads;
579
0
    crdev->num_render_threads = i;
580
0
    crdev->curr_render_thread = 0;
581
0
    crdev->next_band = band;
582
    /* Free up any "reserve" memory we may have allocated, and start the
583
     * threads since we deferred that in the thread setup loop above.
584
     * We know if we get here we can start at least 1 thread.
585
     */
586
0
    for (j=0, code = 0; j<k; j++) {
587
0
        gs_free_object(mem, reserve_memory_array[j], "clist_setup_render_threads");
588
0
        if (code == 0 && j < i)
589
0
            code = clist_start_render_thread(dev, j, crdev->render_threads[j].band);
590
0
    }
591
0
    gs_free_object(mem, reserve_memory_array, "clist_setup_render_threads");
592
593
0
    if(gs_debug[':'] != 0)
594
0
        dmprintf1(mem, "%% Using %d rendering threads\n", i);
595
596
0
    return code;
597
0
}
598
599
/* This is also exported for teardown after background printing */
600
void
601
teardown_device_and_mem_for_thread(gx_device *dev, gp_thread_id thread_id, bool bg_print)
602
0
{
603
0
    gx_device_clist_common *thread_cdev = (gx_device_clist_common *)dev;
604
0
    gx_device_clist_reader *thread_crdev = (gx_device_clist_reader *)dev;
605
0
    gs_memory_t *thread_memory = dev->memory;
606
607
    /* First finish the thread */
608
0
    gp_thread_finish(thread_id);
609
610
0
    if (bg_print) {
611
        /* we are cleaning up a background printing thread, so we clean up similarly to */
612
        /* what is done  by clist_finish_page, but without re-opening the clist files.  */
613
0
        clist_teardown_render_threads(dev); /* we may have used multiple threads */
614
        /* free the thread's icc_table since this was not done by clist_finish_page */
615
0
        clist_free_icc_table(thread_crdev->icc_table, thread_memory);
616
0
        thread_crdev->icc_table = NULL;
617
        /* NB: gdev_prn_free_memory below will free the color_usage_array */
618
0
    } else {
619
        /* make sure this doesn't get freed by gdev_prn_free_memory below */
620
0
        ((gx_device_clist_reader *)thread_cdev)->color_usage_array = NULL;
621
622
        /* For non-bg_print cases the icc_table is shared between devices, but
623
         * is not reference counted or anything. We rely on it being shared with
624
         * and owned by the "parent" device in the interpreter thread, hence
625
         * null it here to avoid it being freed as we cleanup the thread device.
626
         */
627
0
        thread_crdev->icc_table = NULL;
628
0
    }
629
0
    rc_decrement(thread_crdev->icc_cache_cl, "teardown_render_thread");
630
0
    thread_crdev->icc_cache_cl = NULL;
631
    /*
632
     * Free the BufferSpace, close the band files, optionally unlinking them.
633
     * We unlink the files if this call is cleaning up from bg printing.
634
     * Note that the BufferSpace is freed using 'ppdev->buf' so the 'data'
635
     * pointer doesn't need to be the one that the thread started with
636
     */
637
    /* If this thread was being used for background printing and NumRenderingThreads > 0 */
638
    /* the clist_setup_render_threads may have already closed these files                */
639
    /* Note that in the case of back ground printing, we only want to close the instance  */
640
    /* of the files for the reader (hence the final parameter being false). We'll clean  */
641
    /* the original instance of the files in prn_finish_bg_print()                       */
642
0
    if (thread_cdev->page_info.bfile != NULL)
643
0
        thread_cdev->page_info.io_procs->fclose(thread_cdev->page_info.bfile, thread_cdev->page_info.bfname, false);
644
0
    if (thread_cdev->page_info.cfile != NULL)
645
0
        thread_cdev->page_info.io_procs->fclose(thread_cdev->page_info.cfile, thread_cdev->page_info.cfname, false);
646
0
    thread_cdev->page_info.bfile = thread_cdev->page_info.cfile = NULL;
647
0
    thread_cdev->do_not_open_or_close_bandfiles = true; /* we already closed the files */
648
649
0
    gdev_prn_free_memory((gx_device *)thread_cdev);
650
    /* Free the device copy this thread used.  Note that the
651
       deviceN stuff if was allocated and copied earlier for the device
652
       will be freed with this call and the icc_struct ref count will be decremented. */
653
0
    gs_free_object(thread_memory, thread_cdev, "clist_teardown_render_threads");
654
#ifdef DEBUG
655
    dmprintf(thread_memory, "rendering thread ending memory state...\n");
656
    gs_memory_chunk_dump_memory(thread_memory);
657
    dmprintf(thread_memory, "                                    memory dump done.\n");
658
#endif
659
0
    gs_memory_chunk_release(thread_memory);
660
0
}
661
662
void
663
clist_teardown_render_threads(gx_device *dev)
664
596k
{
665
596k
    gx_device_clist *cldev = (gx_device_clist *)dev;
666
596k
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
667
596k
    gx_device_clist_reader *crdev = &cldev->reader;
668
596k
    gs_memory_t *mem = cdev->bandlist_memory;
669
596k
    int i;
670
671
596k
    if (crdev->render_threads != NULL) {
672
        /* Wait for all threads to finish */
673
0
        for (i = (crdev->num_render_threads - 1); i >= 0; i--) {
674
0
            clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
675
676
0
            if (thread->status == THREAD_BUSY)
677
0
                gx_semaphore_wait(thread->sema_this);
678
0
        }
679
        /* then free each thread's memory */
680
0
        for (i = (crdev->num_render_threads - 1); i >= 0; i--) {
681
0
            clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
682
0
            gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;
683
684
            /* Free control semaphores */
685
0
            gx_semaphore_free(thread->sema_group);
686
0
            gx_semaphore_free(thread->sema_this);
687
            /* destroy the thread's buffer device */
688
0
            thread_cdev->buf_procs.destroy_buf_device(thread->bdev);
689
690
0
            if (thread->options) {
691
0
                if (thread->options->free_buffer_fn && thread->buffer) {
692
0
                    thread->options->free_buffer_fn(thread->options->arg, dev, thread->memory, thread->buffer);
693
0
                    thread->buffer = NULL;
694
0
                }
695
0
                thread->options = NULL;
696
0
            }
697
698
            /* before freeing this device's memory, swap with cdev if it was the main_thread_data */
699
0
            if (thread_cdev->data == crdev->main_thread_data) {
700
0
                thread_cdev->data = cdev->data;
701
0
                cdev->data = crdev->main_thread_data;
702
0
            }
703
#ifdef DEBUG
704
            if (gs_debug[':'])
705
                dmprintf2(thread->memory, "%% Thread %d total usertime=%ld msec\n", i, thread->cputime);
706
            dmprintf1(thread->memory, "\nThread %d ", i);
707
#endif
708
0
            teardown_device_and_mem_for_thread((gx_device *)thread_cdev, thread->thread, false);
709
0
        }
710
0
        gs_free_object(mem, crdev->render_threads, "clist_teardown_render_threads");
711
0
        crdev->render_threads = NULL;
712
713
        /* Now re-open the clist temp files so we can write to them */
714
0
        if (cdev->page_info.cfile == NULL) {
715
0
            char fmode[4];
716
717
0
            strcpy(fmode, "a+");        /* file already exists and we want to re-use it */
718
0
            strncat(fmode, gp_fmode_binary_suffix, 1);
719
0
            cdev->page_info.io_procs->fopen(cdev->page_info.cfname, fmode, &cdev->page_info.cfile,
720
0
                                mem, cdev->bandlist_memory, true);
721
0
            cdev->page_info.io_procs->fseek(cdev->page_info.cfile, 0, SEEK_SET, cdev->page_info.cfname);
722
0
            cdev->page_info.io_procs->fopen(cdev->page_info.bfname, fmode, &cdev->page_info.bfile,
723
0
                                mem, cdev->bandlist_memory, false);
724
0
            cdev->page_info.io_procs->fseek(cdev->page_info.bfile, 0, SEEK_SET, cdev->page_info.bfname);
725
0
        }
726
0
    }
727
596k
}
728
729
static int
730
clist_start_render_thread(gx_device *dev, int thread_index, int band)
731
0
{
732
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
733
0
    gx_device_clist_reader *crdev = &cldev->reader;
734
0
    int code;
735
0
    gp_thread_creation_callback_t starter;
736
0
    gx_process_page_options_t* options = crdev->render_threads[thread_index].options;
737
738
0
    crdev->render_threads[thread_index].band = band;
739
740
    /* Finally, fire it up */
741
0
    if (options == NULL || options->output_fn) {
742
        /* Traditional mechanism, using output_fn. Each thread will
743
         * block after it renders until it has been output 'in-order'
744
         * using output_fn. */
745
0
        crdev->render_threads[thread_index].status = THREAD_BUSY;
746
747
0
        starter = clist_render_thread;
748
0
    } else {
749
        /* New mechanism, with output_fn == NULL. process_fn is
750
         * required to both render and output the data. Less blocking
751
         * required, and potentially significant speedups as long as
752
         * output does not need to be 'in-order'. */
753
0
        starter = clist_render_thread_no_output_fn;
754
755
        /* This could pretty much be an assert. */
756
0
        if (options == NULL || options->process_fn == NULL)
757
0
            return_error(gs_error_rangecheck);
758
0
    }
759
0
    code = gp_thread_start(starter,
760
0
                           &(crdev->render_threads[thread_index]),
761
0
                           &(crdev->render_threads[thread_index].thread));
762
763
0
    gp_thread_label(crdev->render_threads[thread_index].thread, "Band");
764
765
0
    return code;
766
0
}
767
768
static void
769
clist_render_thread(void *data)
770
0
{
771
0
    clist_render_thread_control_t *thread = (clist_render_thread_control_t *)data;
772
0
    gx_device *dev = thread->cdev;
773
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
774
0
    gx_device_clist_reader *crdev = &cldev->reader;
775
0
    gx_device *bdev = thread->bdev;
776
0
    gs_int_rect band_rect;
777
0
    byte *mdata = crdev->data + crdev->page_info.tile_cache_size;
778
0
    byte *mlines = (crdev->page_info.line_ptrs_offset == 0 ? NULL : mdata + crdev->page_info.line_ptrs_offset);
779
0
    uint raster = gx_device_raster_plane(dev, NULL);
780
0
    int code;
781
0
    int band_height = crdev->page_info.band_params.BandHeight;
782
0
    int band = thread->band;
783
0
    int band_begin_line = band * band_height;
784
0
    int band_end_line = band_begin_line + band_height;
785
0
    int band_num_lines;
786
#ifdef DEBUG
787
    long starttime[2], endtime[2];
788
789
    gp_get_usertime(starttime); /* thread start time */
790
#endif
791
0
    if (band_end_line > dev->height)
792
0
        band_end_line = dev->height;
793
0
    band_num_lines = band_end_line - band_begin_line;
794
795
0
    code = crdev->buf_procs.setup_buf_device
796
0
            (bdev, mdata, raster, (byte **)mlines, 0, band_num_lines, band_num_lines);
797
0
    band_rect.p.x = 0;
798
0
    band_rect.p.y = band_begin_line;
799
0
    band_rect.q.x = dev->width;
800
0
    band_rect.q.y = band_end_line;
801
0
    if (code >= 0)
802
0
        code = clist_render_rectangle(cldev, &band_rect, bdev, NULL, true);
803
804
0
    if (code >= 0 && thread->options && thread->options->process_fn)
805
0
        code = thread->options->process_fn(thread->options->arg, dev, bdev, &band_rect, thread->buffer);
806
807
    /* Reset the band boundaries now */
808
0
    crdev->ymin = band_begin_line;
809
0
    crdev->ymax = band_end_line;
810
0
    crdev->offset_map = NULL;
811
0
    if (code < 0)
812
0
        thread->status = THREAD_ERROR;          /* shouldn't happen */
813
0
    else
814
0
        thread->status = THREAD_DONE;    /* OK */
815
816
#ifdef DEBUG
817
    gp_get_usertime(endtime);
818
    thread->cputime += (endtime[0] - starttime[0]) * 1000 +
819
             (endtime[1] - starttime[1]) / 1000000;
820
#endif
821
    /*
822
     * Signal the semaphores. We signal the 'group' first since even if
823
     * the waiter is released on the group, it still needs to check
824
     * status on the thread
825
     */
826
0
    gx_semaphore_signal(thread->sema_group);
827
0
    gx_semaphore_signal(thread->sema_this);
828
0
}
829
830
/* Used if output_fn == NULL. No blocking required as we no longer need to
831
 * serialise the calls to output_fn. */
832
static void
833
clist_render_thread_no_output_fn(void* data)
834
0
{
835
0
    clist_render_thread_control_t* thread = (clist_render_thread_control_t*)data;
836
0
    gx_device* dev = thread->cdev;
837
0
    gx_device_clist* cldev = (gx_device_clist*)dev;
838
0
    gx_device_clist_reader* crdev = &cldev->reader;
839
0
    gx_device* bdev = thread->bdev;
840
0
    gs_int_rect band_rect;
841
0
    byte* mdata = crdev->data + crdev->page_info.tile_cache_size;
842
0
    byte* mlines = (crdev->page_info.line_ptrs_offset == 0 ? NULL : mdata + crdev->page_info.line_ptrs_offset);
843
0
    uint raster = gx_device_raster_plane(dev, NULL);
844
0
    int code;
845
0
    int band_height = crdev->page_info.band_params.BandHeight;
846
0
    int band_begin_line = thread->band * band_height;
847
0
    int band_end_line = band_begin_line + band_height;
848
0
    int band_num_lines;
849
850
#ifdef DEBUG
851
    long starttime[2], endtime[2];
852
853
    gp_get_usertime(starttime); /* thread start time */
854
#endif
855
    /*
856
      As long there is no need for calling the bands 'in order' we can
857
      process multiple bands with one thread while there are free
858
      (unprocessed) bands available. The only potential race condition
859
      here is where we need to pick a new band to draw when a thread
860
      finishes its render task. We delegate the job of doing this to the
861
      process_fn itself, enabling devices to make use of less expensive
862
      operations (such as fast interlocked exchange with add) that may
863
      not be available portably.
864
      If there is no more unprocessed band the thread will finish, giving
865
      back the control to the main process (obviously after ALL the
866
      rendering threads have got finished).
867
    */
868
869
0
    while (band_begin_line < dev->height && band_end_line > 0) {
870
0
        if (band_end_line > dev->height)
871
0
            band_end_line = dev->height;
872
0
        band_num_lines = band_end_line - band_begin_line;
873
874
0
        ((gx_device_memory*)bdev)->band_y = band_begin_line; /* probably useless, but doesn't hurt */
875
876
0
        code = crdev->buf_procs.setup_buf_device
877
0
        (bdev, mdata, raster, (byte**)mlines, 0, band_num_lines, band_num_lines);
878
879
0
        band_rect.p.x = 0;
880
0
        band_rect.p.y = band_begin_line;
881
0
        band_rect.q.x = dev->width;
882
0
        band_rect.q.y = band_end_line;
883
884
0
        if (code >= 0)
885
0
            code = clist_render_rectangle(cldev, &band_rect, bdev, NULL, true);
886
887
0
        if (code >= 0)
888
0
            code = thread->options->process_fn(thread->options->arg, dev, bdev, &band_rect, thread->buffer);
889
890
        /* A side effect of the process_fn is that it should select the next
891
          * band for this thread to render. It does this by updating
892
          * crdev->next_band (that's the thread's own device!), as well as
893
          * updating crdev_orig so that other threads won't try to render the
894
          * same thread (normally by updating crdev_orig->next_band). */
895
0
        band_begin_line = crdev->next_band * band_height;
896
0
        band_end_line = band_begin_line + band_height;
897
0
        if (code < 0)
898
0
            break;
899
0
    }
900
901
902
#ifdef DEBUG
903
    gp_get_usertime(endtime);
904
    thread->cputime += (endtime[0] - starttime[0]) * 1000 +
905
        (endtime[1] - starttime[1]) / 1000000;
906
#endif
907
0
    if (code < 0)
908
0
        thread->status = THREAD_ERROR;          /* shouldn't happen */
909
0
    else
910
0
        thread->status = THREAD_DONE;    /* OK */
911
0
}
912
913
/*
914
 * Copy the raster data from the completed thread to the caller's
915
 * device (the main thread)
916
 * Return 0 if OK, < 0 is the error code from the thread
917
 *
918
 * After swapping the pointers, start up the completed thread with the
919
 * next band remaining to do (if any)
920
 */
921
static int
922
clist_get_band_from_thread(gx_device *dev, int band_needed, gx_process_page_options_t *options)
923
0
{
924
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
925
0
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
926
0
    gx_device_clist_reader *crdev = &cldev->reader;
927
0
    int i, code = 0;
928
0
    int thread_index = crdev->curr_render_thread;
929
0
    clist_render_thread_control_t *thread = &(crdev->render_threads[thread_index]);
930
0
    gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;
931
0
    int band_height = crdev->page_info.band_params.BandHeight;
932
0
    int band_count = cdev->nbands;
933
0
    byte *tmp;                  /* for swapping data areas */
934
935
    /* We expect that the thread needed will be the 'current' thread */
936
0
    if (thread->band != band_needed) {
937
0
        int band = band_needed;
938
939
0
        emprintf3(thread->memory,
940
0
                  "thread->band = %d, band_needed = %d, direction = %d, ",
941
0
                  thread->band, band_needed, crdev->thread_lookahead_direction);
942
943
        /* Probably we went in the wrong direction, so let the threads */
944
        /* all complete, then restart them in the opposite direction   */
945
        /* If the caller is 'bouncing around' we may end up back here, */
946
        /* but that is a VERY rare case (we haven't seen it yet).      */
947
0
        for (i=0; i < crdev->num_render_threads; i++) {
948
0
            clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
949
950
0
            if (thread->status == THREAD_BUSY)
951
0
                gx_semaphore_wait(thread->sema_this);
952
0
        }
953
0
        crdev->thread_lookahead_direction *= -1;      /* reverse direction (but may be overruled below) */
954
0
        if (band_needed == band_count-1)
955
0
            crdev->thread_lookahead_direction = -1;   /* assume backwards if we are asking for the last band */
956
0
        if (band_needed == 0)
957
0
            crdev->thread_lookahead_direction = 1;    /* force forward if we are looking for band 0 */
958
959
0
        dmprintf1(thread->memory, "new_direction = %d\n", crdev->thread_lookahead_direction);
960
961
        /* Loop starting the threads in the new lookahead_direction */
962
0
        for (i=0; (i < crdev->num_render_threads) && (band >= 0) && (band < band_count);
963
0
                i++, band += crdev->thread_lookahead_direction) {
964
0
            thread = &(crdev->render_threads[i]);
965
0
            thread->band = -1;          /* a value that won't match any valid band */
966
            /* Start thread 'i' to do band */
967
0
            if ((code = clist_start_render_thread(dev, i, band)) < 0)
968
0
                break;
969
0
        }
970
0
        crdev->next_band = i;     /* may be < 0 or == band_count, but that is handled later */
971
0
        crdev->curr_render_thread = thread_index = 0;
972
0
        thread = &(crdev->render_threads[0]);
973
0
        thread_cdev = (gx_device_clist_common *)thread->cdev;
974
0
    }
975
    /* Wait for this thread */
976
0
    gx_semaphore_wait(thread->sema_this);
977
0
    gp_thread_finish(thread->thread);
978
0
    thread->thread = NULL;
979
0
    if (thread->status == THREAD_ERROR)
980
0
        return_error(gs_error_unknownerror);          /* FAIL */
981
982
0
    if (options && options->output_fn) {
983
0
        code = options->output_fn(options->arg, dev, thread->buffer);
984
0
        if (code < 0)
985
0
            return code;
986
0
    }
987
988
    /* Swap the data areas to avoid the copy */
989
0
    tmp = cdev->data;
990
0
    cdev->data = thread_cdev->data;
991
0
    thread_cdev->data = tmp;
992
0
    thread->status = THREAD_IDLE;        /* the data is no longer valid */
993
0
    thread->band = -1;
994
    /* Update the bounds for this band */
995
0
    cdev->ymin =  band_needed * band_height;
996
0
    cdev->ymax =  cdev->ymin + band_height;
997
0
    if (cdev->ymax > dev->height)
998
0
        cdev->ymax = dev->height;
999
1000
0
    if (crdev->next_band >= 0 && crdev->next_band < band_count) {
1001
0
        code = clist_start_render_thread(dev, thread_index, crdev->next_band);
1002
0
        crdev->next_band += crdev->thread_lookahead_direction;
1003
0
    }
1004
    /* bump the 'curr' to the next thread */
1005
0
    crdev->curr_render_thread = crdev->curr_render_thread == crdev->num_render_threads - 1 ?
1006
0
                0 : crdev->curr_render_thread + 1;
1007
1008
0
    return code;
1009
0
}
1010
1011
/* Copy a rasterized rectangle to the client, rasterizing if needed. */
1012
/* The first invocation starts multiple threads to perform "look ahead" */
1013
/* rendering adjacent to the first band (forward or backward) */
1014
static int
1015
clist_get_bits_rect_mt(gx_device *dev, const gs_int_rect * prect,
1016
                         gs_get_bits_params_t *params)
1017
0
{
1018
0
    gx_device_printer *pdev = (gx_device_printer *)dev;
1019
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
1020
0
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
1021
0
    gx_device_clist_reader *crdev = &cldev->reader;
1022
0
    gs_memory_t *mem = cdev->bandlist_memory;
1023
0
    gs_get_bits_options_t options = params->options;
1024
0
    int y = prect->p.y;
1025
0
    int end_y = prect->q.y;
1026
0
    int line_count = end_y - y;
1027
0
    int band_height = crdev->page_info.band_params.BandHeight;
1028
0
    int band = y / band_height;
1029
0
    gs_int_rect band_rect;
1030
0
    int lines_rasterized;
1031
0
    gx_device *bdev;
1032
0
    byte *mdata;
1033
0
    uint raster = gx_device_raster(dev, 1);
1034
0
    int my;
1035
0
    int code = 0;
1036
1037
    /* This page might not want multiple threads */
1038
    /* Also we don't support plane extraction using multiple threads */
1039
0
    if (pdev->num_render_threads_requested < 1 || (options & GB_SELECT_PLANES))
1040
0
        return clist_get_bits_rectangle(dev, prect, params);
1041
1042
0
    if (prect->p.x < 0 || prect->q.x > dev->width ||
1043
0
        y < 0 || end_y > dev->height
1044
0
        )
1045
0
        return_error(gs_error_rangecheck);
1046
0
    if (line_count <= 0 || prect->p.x >= prect->q.x)
1047
0
        return 0;
1048
1049
0
    if (crdev->ymin < 0)
1050
0
        if ((code = clist_close_writer_and_init_reader(cldev)) < 0)
1051
0
            return code; /* can't recover from this */
1052
1053
0
    if (crdev->ymin == 0 && crdev->ymax == 0 && crdev->render_threads == NULL) {
1054
        /* Haven't done any rendering yet, try to set up the threads */
1055
0
        if (clist_setup_render_threads(dev, y, NULL) < 0)
1056
            /* problem setting up the threads, revert to single threaded */
1057
0
            return clist_get_bits_rectangle(dev, prect, params);
1058
0
    } else {
1059
0
        if (crdev->render_threads == NULL) {
1060
            /* If we get here with with ymin and ymax > 0 it's because we closed the threads */
1061
            /* while doing a page due to an error. Use single threaded mode.         */
1062
0
            return clist_get_bits_rectangle(dev, prect, params);
1063
0
        }
1064
0
    }
1065
    /* If we already have the band's data, just return it */
1066
0
    if (y < crdev->ymin || end_y > crdev->ymax)
1067
0
        code = clist_get_band_from_thread(dev, band, NULL);
1068
0
    if (code < 0)
1069
0
        goto free_thread_out;
1070
0
    mdata = crdev->data + crdev->page_info.tile_cache_size;
1071
0
    if ((code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
1072
0
                                  &bdev, cdev->target, y, NULL,
1073
0
                                  mem, &(crdev->color_usage_array[band]))) < 0 ||
1074
0
        (code = crdev->buf_procs.setup_buf_device(bdev, mdata, raster, NULL,
1075
0
                            y - crdev->ymin, line_count, crdev->ymax - crdev->ymin)) < 0)
1076
0
        goto free_thread_out;
1077
1078
0
    lines_rasterized = min(band_height, line_count);
1079
    /* Return as much of the rectangle as falls within the rasterized lines. */
1080
0
    band_rect = *prect;
1081
0
    band_rect.p.y = 0;
1082
0
    band_rect.q.y = lines_rasterized;
1083
0
    code = dev_proc(bdev, get_bits_rectangle)
1084
0
        (bdev, &band_rect, params);
1085
0
    cdev->buf_procs.destroy_buf_device(bdev);
1086
0
    if (code < 0)
1087
0
        goto free_thread_out;
1088
1089
    /* Note that if called via 'get_bits', the line count will always be 1 */
1090
0
    if (lines_rasterized == line_count) {
1091
0
        return code;
1092
0
    }
1093
1094
/***** TODO: Handle the below with data from the threads *****/
1095
    /*
1096
     * We'll have to return the rectangle in pieces.  Force GB_RETURN_COPY
1097
     * rather than GB_RETURN_POINTER, and require all subsequent pieces to
1098
     * use the same values as the first piece for all of the other format
1099
     * options.  If copying isn't allowed, or if there are any unread
1100
     * rectangles, punt.
1101
     */
1102
0
    if (!(options & GB_RETURN_COPY) || code > 0)
1103
0
        return gx_default_get_bits_rectangle(dev, prect, params);
1104
0
    options = params->options;
1105
0
    if (!(options & GB_RETURN_COPY)) {
1106
        /* Redo the first piece with copying. */
1107
0
        params->options = (params->options & ~GB_RETURN_ALL) | GB_RETURN_COPY;
1108
0
        lines_rasterized = 0;
1109
0
    }
1110
0
    {
1111
0
        gs_get_bits_params_t band_params;
1112
0
        uint raster = gx_device_raster(bdev, true);
1113
1114
0
        code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
1115
0
                                      &bdev, cdev->target, y, NULL,
1116
0
                                      mem, &(crdev->color_usage_array[band]));
1117
0
        if (code < 0)
1118
0
            return code;
1119
0
        band_params = *params;
1120
0
        while ((y += lines_rasterized) < end_y) {
1121
            /* Increment data pointer by lines_rasterized. */
1122
0
            band_params.data[0] += raster * lines_rasterized;
1123
0
            line_count = end_y - y;
1124
0
            code = clist_rasterize_lines(dev, y, line_count, bdev, NULL, &my);
1125
0
            if (code < 0)
1126
0
                break;
1127
0
            lines_rasterized = min(code, line_count);
1128
0
            band_rect.p.y = my;
1129
0
            band_rect.q.y = my + lines_rasterized;
1130
0
            code = dev_proc(bdev, get_bits_rectangle)
1131
0
                (bdev, &band_rect, &band_params);
1132
0
            if (code < 0)
1133
0
                break;
1134
0
            params->options = band_params.options;
1135
0
            if (lines_rasterized == line_count)
1136
0
                break;
1137
0
        }
1138
0
        cdev->buf_procs.destroy_buf_device(bdev);
1139
0
    }
1140
0
    return code;
1141
1142
/* Free up thread stuff */
1143
0
free_thread_out:
1144
0
    clist_teardown_render_threads(dev);
1145
0
    return code;
1146
0
}
1147
1148
int
1149
clist_process_page(gx_device *dev, gx_process_page_options_t *options)
1150
0
{
1151
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
1152
0
    gx_device_clist_reader *crdev = &cldev->reader;
1153
0
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
1154
0
    int y;
1155
0
    int line_count;
1156
0
    int band_height = crdev->page_info.band_params.BandHeight;
1157
0
    gs_int_rect band_rect;
1158
0
    int lines_rasterized;
1159
0
    gx_device *bdev;
1160
0
    gx_render_plane_t render_plane;
1161
0
    int my;
1162
0
    int code;
1163
0
    void *buffer = NULL;
1164
1165
0
    if (0 > (code = clist_close_writer_and_init_reader(cldev)))
1166
0
        return code;
1167
1168
0
    if (options->init_buffer_fn) {
1169
0
        code = options->init_buffer_fn(options->arg, dev, dev->memory, dev->width, band_height, &buffer);
1170
0
        if (code < 0)
1171
0
            return code;
1172
0
    }
1173
1174
0
    gx_render_plane_init(&render_plane, dev, -1);
1175
0
    for (y = 0; y < dev->height; y += lines_rasterized)
1176
0
    {
1177
0
        line_count = band_height;
1178
0
        if (line_count > dev->height - y)
1179
0
            line_count = dev->height - y;
1180
0
        code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
1181
0
                                      &bdev, cdev->target, y, &render_plane,
1182
0
                                      dev->memory,
1183
0
                                      &(crdev->color_usage_array[y/band_height]));
1184
0
        if (code < 0)
1185
0
            return code;
1186
0
        code = clist_rasterize_lines(dev, y, line_count, bdev, &render_plane, &my);
1187
0
        if (code >= 0)
1188
0
        {
1189
0
            lines_rasterized = min(code, line_count);
1190
1191
            /* Return as much of the rectangle as falls within the rasterized lines. */
1192
0
            band_rect.p.x = 0;
1193
0
            band_rect.p.y = y;
1194
0
            band_rect.q.x = dev->width;
1195
0
            band_rect.q.y = y + lines_rasterized;
1196
0
            if (options->process_fn)
1197
0
                code = options->process_fn(options->arg, dev, bdev, &band_rect, buffer);
1198
0
        }
1199
0
        if (code >= 0 && options->output_fn)
1200
0
            code = options->output_fn(options->arg, dev, buffer);
1201
0
        cdev->buf_procs.destroy_buf_device(bdev);
1202
0
        if (code < 0)
1203
0
            break;
1204
0
    }
1205
1206
0
    if (options->free_buffer_fn) {
1207
0
        options->free_buffer_fn(options->arg, dev, dev->memory, buffer);
1208
0
    }
1209
1210
0
    return code;
1211
0
}
1212
1213
static int
1214
clist_process_page_mt(gx_device *dev, gx_process_page_options_t *options)
1215
0
{
1216
0
    gx_device_printer *pdev = (gx_device_printer *)dev;
1217
0
    gx_device_clist *cldev = (gx_device_clist *)dev;
1218
0
    gx_device_clist_reader *crdev = &cldev->reader;
1219
0
    int band_height = crdev->page_info.band_params.BandHeight;
1220
0
    int band;
1221
0
    int num_bands = (dev->height + band_height-1)/band_height;
1222
0
    int code;
1223
0
    int reverse = !!(options->options & GX_PROCPAGE_BOTTOM_UP);
1224
1225
    /* This page might not want multiple threads */
1226
    /* Also we don't support plane extraction using multiple threads */
1227
0
    if (pdev->num_render_threads_requested < 1)
1228
0
        return clist_process_page(dev, options);
1229
1230
0
    if ((code = clist_close_writer_and_init_reader(cldev)) < 0)
1231
0
        return code; /* can't recover from this */
1232
1233
    /* Haven't done any rendering yet, try to set up the threads */
1234
0
    if (clist_setup_render_threads(dev, reverse ? dev->height-1 : 0, options) < 0)
1235
        /* problem setting up the threads, revert to single threaded */
1236
0
        return clist_process_page(dev, options);
1237
1238
0
    if (options->output_fn) {
1239
        /* Traditional mechanism: The rendering threads are running. We wait for them
1240
         * to finish in order, call output_fn with the results, and kick off the
1241
         * next bands rendering. This means that threads block after they finish
1242
         * rendering until it is their turn to call output_fn. */
1243
0
        if (reverse)
1244
0
        {
1245
0
            for (band = num_bands - 1; band > 0; band--)
1246
0
            {
1247
0
                code = clist_get_band_from_thread(dev, band, options);
1248
0
                if (code < 0)
1249
0
                    goto free_thread_out;
1250
0
            }
1251
0
        }
1252
0
        else
1253
0
        {
1254
0
            for (band = 0; band < num_bands; band++)
1255
0
            {
1256
0
                code = clist_get_band_from_thread(dev, band, options);
1257
0
                if (code < 0)
1258
0
                    goto free_thread_out;
1259
0
            }
1260
0
        }
1261
0
    }
1262
0
    else
1263
0
    {
1264
        /* New mechanism: The rendering threads are running. Each of them will
1265
         * automatically loop; rendering the next band necessary, outputting the
1266
         * results (directly, as there is no output_fn to call), and selecting
1267
         * the next band to operate on. The threads will exit once there are no
1268
         * more bands to render/output. All we need do here, therefore, is wait
1269
         * for them to exit. */
1270
0
        int i;
1271
0
        int failed = 0;
1272
0
        for (i = 0; i < crdev->num_render_threads; i++) {
1273
0
            gp_thread_finish(crdev->render_threads[i].thread);
1274
0
            if (crdev->render_threads[i].status == THREAD_ERROR)
1275
0
                failed = 1;
1276
0
            crdev->render_threads[i].thread = 0;
1277
0
        }
1278
0
        if (failed)
1279
0
            code = gs_note_error(gs_error_unknownerror);
1280
0
    }
1281
1282
    /* Always free up thread stuff before exiting */
1283
0
free_thread_out:
1284
0
    clist_teardown_render_threads(dev);
1285
0
    return code;
1286
0
}
1287
1288
static void
1289
test_threads(void *dummy)
1290
0
{
1291
0
}
1292
1293
int
1294
clist_enable_multi_thread_render(gx_device *dev)
1295
0
{
1296
0
    int code = -1;
1297
0
    gp_thread_id thread;
1298
1299
0
    if (dev->procs.get_bits_rectangle == clist_get_bits_rect_mt)
1300
0
        return 1; /* no need to test again */
1301
    /* We need to test gp_thread_start since we may be on a platform  */
1302
    /* built without working threads, i.e., using gp_nsync.c dummy    */
1303
    /* routines. The nosync gp_thread_start returns a -ve error code. */
1304
0
    if ((code = gp_thread_start(test_threads, NULL, &thread)) < 0 ) {
1305
0
        return code;    /* Threads don't work */
1306
0
    }
1307
0
    gp_thread_label(thread, "test_thread");
1308
0
    gp_thread_finish(thread);
1309
0
    set_dev_proc(dev, get_bits_rectangle, clist_get_bits_rect_mt);
1310
0
    set_dev_proc(dev, process_page, clist_process_page_mt);
1311
1312
0
    return 1;
1313
0
}