/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(¶mlist, thread_mem); |
153 | 0 | if ((code = gs_getdeviceparams(dev, (gs_param_list *)¶mlist)) < 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(¶mlist); |
160 | 0 | if ((code = gs_putdeviceparams(ndev, (gs_param_list *)¶mlist)) < 0) |
161 | 0 | goto out_cleanup; |
162 | 0 | gs_c_param_list_release(¶mlist); |
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 | } |