Coverage Report

Created: 2022-04-16 11:23

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