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