Coverage Report

Created: 2022-10-31 07:00

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