Exemple #1
0
/* rendering adjacent to the first band (forward or backward) */
static int
clist_get_bits_rect_mt(gx_device *dev, const gs_int_rect * prect,
			 gs_get_bits_params_t *params, gs_int_rect **unread)
{
    gx_device_printer *pdev = (gx_device_printer *)dev;
    gx_device_clist *cldev = (gx_device_clist *)dev;
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
    gx_device_clist_reader *crdev = &cldev->reader;
    gs_memory_t *mem = cdev->bandlist_memory;
    gs_get_bits_options_t options = params->options;
    int y = prect->p.y;
    int end_y = prect->q.y;
    int line_count = end_y - y;
    int band_height = crdev->page_info.band_params.BandHeight;
    int band = y / band_height;
    gs_int_rect band_rect;
    int lines_rasterized;
    gx_device *bdev;
    byte *mdata;
    uint raster = bitmap_raster(dev->width * dev->color_info.depth);
    int my;
    int code = 0;

    /* This page might not want multiple threads */
    /* Also we don't support plane extraction using multiple threads */
    if (pdev->num_render_threads_requested < 1 || (options & GB_SELECT_PLANES))
	return clist_get_bits_rectangle(dev, prect, params, unread);

    if (prect->p.x < 0 || prect->q.x > dev->width ||
	y < 0 || end_y > dev->height
	)
	return_error(gs_error_rangecheck);
    if (line_count <= 0 || prect->p.x >= prect->q.x)
	return 0;

    if (crdev->ymin < 0) {
	if ((code = clist_close_writer_and_init_reader(cldev)) < 0)
	    return code;
	if (clist_setup_render_threads(dev, y) < 0) 
	    /* problem setting up the threads, revert to single threaded */
	    return clist_get_bits_rectangle(dev, prect, params, unread);
    }
    else {
	if (crdev->render_threads == NULL) {
            /* If we get here with with ymin >=0, it's because we closed the threads */
	    /* while doing a page due to an error. Use single threaded mode.         */
	    return clist_get_bits_rectangle(dev, prect, params, unread);
	}
    }
    /* If we already have the band's data, just return it */
    if (y < crdev->ymin || end_y > crdev->ymax)
	code = clist_get_band_from_thread(dev, band);
    if (code < 0)
	goto free_thread_out;
    mdata = crdev->data + crdev->page_tile_cache_size;
    if ((code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
				  &bdev, cdev->target, y, NULL,
				  mem, clist_get_band_complexity(dev,y))) < 0 ||
	(code = crdev->buf_procs.setup_buf_device(bdev, mdata, raster, NULL,
			    y - crdev->ymin, line_count, crdev->ymax - crdev->ymin)) < 0)
	goto free_thread_out;

    lines_rasterized = min(band_height, line_count);
    /* Return as much of the rectangle as falls within the rasterized lines. */
    band_rect = *prect;
    band_rect.p.y = 0;
    band_rect.q.y = lines_rasterized;
    code = dev_proc(bdev, get_bits_rectangle)
	(bdev, &band_rect, params, unread);
    cdev->buf_procs.destroy_buf_device(bdev);
    if (code < 0)
	goto free_thread_out;

    /* Note that if called via 'get_bits', the line count will always be 1 */
    if (lines_rasterized == line_count) {
	return code;		
    }

/***** TODO: Handle the below with data from the threads *****/
    /*
     * We'll have to return the rectangle in pieces.  Force GB_RETURN_COPY
     * rather than GB_RETURN_POINTER, and require all subsequent pieces to
     * use the same values as the first piece for all of the other format
     * options.  If copying isn't allowed, or if there are any unread
     * rectangles, punt.
     */
    if (!(options & GB_RETURN_COPY) || code > 0)
	return gx_default_get_bits_rectangle(dev, prect, params, unread);
    options = params->options;
    if (!(options & GB_RETURN_COPY)) {
	/* Redo the first piece with copying. */
	params->options = options =
	    (params->options & ~GB_RETURN_ALL) | GB_RETURN_COPY;
	lines_rasterized = 0;
    }
    {
	gs_get_bits_params_t band_params;
	uint raster = gx_device_raster(bdev, true);

	code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
				      &bdev, cdev->target, y, NULL,
				      mem, clist_get_band_complexity(dev, y));
	if (code < 0)
	    return code;
	band_params = *params;
	while ((y += lines_rasterized) < end_y) {
	    /* Increment data pointer by lines_rasterized. */
	    if (band_params.data)
		band_params.data[0] += raster * lines_rasterized;
	    line_count = end_y - y;
	    code = clist_rasterize_lines(dev, y, line_count, bdev, NULL, &my);
	    if (code < 0)
		break;
	    lines_rasterized = min(code, line_count);
	    band_rect.p.y = my;
	    band_rect.q.y = my + lines_rasterized;
	    code = dev_proc(bdev, get_bits_rectangle)
		(bdev, &band_rect, &band_params, unread);
	    if (code < 0)
		break;
	    params->options = options = band_params.options;
	    if (lines_rasterized == line_count)
		break;
	}
	cdev->buf_procs.destroy_buf_device(bdev);
    }
    return code;

/* Free up thread stuff */
free_thread_out:
    clist_teardown_render_threads(dev);
    return code;
}
Exemple #2
0
int
clist_process_page(gx_device *dev, gx_process_page_options_t *options)
{
    gx_device_clist *cldev = (gx_device_clist *)dev;
    gx_device_clist_reader *crdev = &cldev->reader;
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
    int y;
    int line_count;
    int band_height = crdev->page_band_height;
    gs_int_rect band_rect;
    int lines_rasterized;
    gx_device *bdev;
    gx_render_plane_t render_plane;
    int my;
    int code;
    void *buffer = NULL;

    if (0 > (code = clist_close_writer_and_init_reader(cldev)))
        return code;

    if (options->init_buffer_fn) {
        code = options->init_buffer_fn(options->arg, dev, dev->memory, dev->width, band_height, &buffer);
        if (code < 0)
            return code;
    }

    render_plane.index = -1;
    for (y = 0; y < dev->height; y += lines_rasterized)
    {
        line_count = band_height;
        if (line_count > dev->height - y)
            line_count = dev->height - y;
        code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
                                      &bdev, cdev->target, y, &render_plane,
                                      dev->memory,
                                      &(crdev->color_usage_array[y/band_height]));
        if (code < 0)
            return code;
        code = clist_rasterize_lines(dev, y, line_count, bdev, &render_plane, &my);
        if (code >= 0)
        {
            lines_rasterized = min(code, line_count);

            /* Return as much of the rectangle as falls within the rasterized lines. */
            band_rect.p.x = 0;
            band_rect.p.y = y;
            band_rect.q.x = dev->width;
            band_rect.q.y = y + lines_rasterized;
            if (options->process_fn)
                code = options->process_fn(options->arg, dev, bdev, &band_rect, buffer);
        }
        if (code >= 0 && options->output_fn)
            code = options->output_fn(options->arg, dev, buffer);
        cdev->buf_procs.destroy_buf_device(bdev);
        if (code < 0)
            break;
    }

    if (options->free_buffer_fn) {
        options->free_buffer_fn(options->arg, dev, dev->memory, buffer);
    }

    return code;
}
Exemple #3
0
/* Set up and start the render threads */
static int
clist_setup_render_threads(gx_device *dev, int y)
{
    gx_device_printer *pdev = (gx_device_printer *)dev;
    gx_device_clist *cldev = (gx_device_clist *)dev;
    gx_device_clist_common *cdev = (gx_device_clist_common *)cldev;
    gx_device_clist_reader *crdev = &cldev->reader;
    gs_memory_t *mem = cdev->bandlist_memory;
    gx_device *protodev;
    gs_c_param_list paramlist;
    int i, code, band;
    int band_count = cdev->nbands;
    char fmode[4];

    crdev->num_render_threads = pdev->num_render_threads_requested;

    if(gs_debug[':'] != 0)
	dprintf1("%% %d rendering threads requested.\n", pdev->num_render_threads_requested);

    if (crdev->num_render_threads > band_count)
	crdev->num_render_threads = band_count;	/* don't bother starting more threads than bands */

    /* Allocate and initialize an array of thread control structures */
    crdev->render_threads = (clist_render_thread_control_t *)
	      gs_alloc_byte_array(mem, crdev->num_render_threads,
	      sizeof(clist_render_thread_control_t), "clist_setup_render_threads" );
    /* fallback to non-threaded if allocation fails */
    if (crdev->render_threads == NULL) {
	eprintf(" VMerror prevented threads from starting.\n");
	return_error(gs_error_VMerror);
    }


    memset(crdev->render_threads, 0, crdev->num_render_threads *
	    sizeof(clist_render_thread_control_t));
    crdev->main_thread_data = cdev->data;		/* save data area */
    /* Based on the line number requested, decide the order of band rendering */
    /* Almost all devices go in increasing line order (except the bmp* devices ) */
    crdev->thread_lookahead_direction = (y < (cdev->height - 1)) ? 1 : -1;
    band = y / crdev->page_info.band_params.BandHeight;

    /* Close the files so we can open them in multiple threads */
    if ((code = cdev->page_info.io_procs->fclose(cdev->page_cfile, cdev->page_cfname, false)) < 0 ||
        (code = cdev->page_info.io_procs->fclose(cdev->page_bfile, cdev->page_bfname, false)) < 0) {
	gs_free_object(mem, crdev->render_threads, "clist_setup_render_threads");
	crdev->render_threads = NULL;
	eprintf("Closing clist files prevented threads from starting.\n");
        return_error(gs_error_unknownerror); /* shouldn't happen */
    }
    cdev->page_cfile = cdev->page_bfile = NULL;
    strcpy(fmode, "r");			/* read access for threads */
    strncat(fmode, gp_fmode_binary_suffix, 1);
    /* Find the prototype for this device (needed so we can copy from it) */
    for (i=0; (protodev = (gx_device *)gs_getdevice(i)) != NULL; i++)
	if (strcmp(protodev->dname, dev->dname) == 0)
	    break;
    if (protodev == NULL) {
	eprintf("Could not find prototype device. Rendering threads not started.\n");
	return gs_error_rangecheck;
    }

    gs_c_param_list_write(&paramlist, mem);
    if ((code = gs_getdeviceparams(dev, (gs_param_list *)&paramlist)) < 0) {
	eprintf1("Error getting device params, code=%d. Rendering threads not started.\n", code);
	return code;
    }

    /* Loop creating the devices and semaphores for each thread, then start them */
    for (i=0; (i < crdev->num_render_threads) && (band >= 0) && (band < band_count);
	    i++, band += crdev->thread_lookahead_direction) {
	gx_device *ndev;
	gx_device_clist *ncldev;
	gx_device_clist_common *ncdev;
	clist_render_thread_control_t *thread = &(crdev->render_threads[i]);

	/* Every thread will have a 'chunk allocator' to reduce the interaction
	 * with the 'base' allocator which has 'mutex' (locking) protection. 
	 * This improves performance of the threads.
	 */
	if ((code = gs_memory_chunk_wrap(&(thread->memory), mem )) < 0) {
	    eprintf1("chunk_wrap returned error code: %d\n", code);
	    break;
	}

        thread->band = -1;		/* a value that won't match any valid band */
	if ((code = gs_copydevice((gx_device **) &ndev, protodev, thread->memory)) < 0) {
	    code = 0;		/* even though we failed, no cleanup needed */
	    break;
	}
	ncldev = (gx_device_clist *)ndev;
	ncdev = (gx_device_clist_common *)ndev;
	gx_device_fill_in_procs(ndev);
	((gx_device_printer *)ncdev)->buffer_memory = ncdev->memory =
		ncdev->bandlist_memory = thread->memory;
	gs_c_param_list_read(&paramlist);
	ndev->PageCount = dev->PageCount;	/* copy to prevent mismatch error */
	if ((code = gs_putdeviceparams(ndev, (gs_param_list *)&paramlist)) < 0)
	    break;
	ncdev->page_uses_transparency = cdev->page_uses_transparency;
	/* gdev_prn_allocate_memory sets the clist for writing, creating new files.
	 * We need  to unlink those files and open the main thread's files, then
	 * reset the clist state for reading/rendering
	 */
	if ((code = gdev_prn_allocate_memory(ndev, NULL, ndev->width, ndev->height)) < 0)
	    break;
	thread->cdev = ndev;
	/* close and unlink the temp files just created */
	cdev->page_info.io_procs->fclose(ncdev->page_cfile, ncdev->page_cfname, true);
	cdev->page_info.io_procs->fclose(ncdev->page_bfile, ncdev->page_bfname, true);
	/* open the main thread's files for this thread */
	if ((code=cdev->page_info.io_procs->fopen(cdev->page_cfname, fmode, &ncdev->page_cfile,
			    thread->memory, thread->memory, true)) < 0 ||
	     (code=cdev->page_info.io_procs->fopen(cdev->page_bfname, fmode, &ncdev->page_bfile,
			    thread->memory, thread->memory, false)) < 0)
	    break;
	clist_render_init(ncldev);	/* Initialize clist device for reading */
	ncdev->page_bfile_end_pos = cdev->page_bfile_end_pos;

	/* create the buf device for this thread, and allocate the semaphores */
	if ((code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
				&(thread->bdev), cdev->target,
				band*crdev->page_band_height, NULL,
				thread->memory, clist_get_band_complexity(dev,y)) < 0)) 
	    break;
	if ((thread->sema_this = gx_semaphore_alloc(thread->memory)) == NULL ||
	    (thread->sema_group = gx_semaphore_alloc(thread->memory)) == NULL) {
	    code = gs_error_VMerror;
	    break;
	}
	/* Start thread 'i' to do band */
	if ((code = clist_start_render_thread(dev, i, band)) < 0)
	    break;
    }
    gs_c_param_list_release(&paramlist);
    /* If the code < 0, the last thread creation failed -- clean it up */
    if (code < 0) {
	/* the following relies on 'free' ignoring NULL pointers */
	gx_semaphore_free(crdev->render_threads[i].sema_group); 
	gx_semaphore_free(crdev->render_threads[i].sema_this); 
	if (crdev->render_threads[i].bdev != NULL)
	    cdev->buf_procs.destroy_buf_device(crdev->render_threads[i].bdev);
	if (crdev->render_threads[i].cdev != NULL) {
	    gx_device_clist_common *thread_cdev = (gx_device_clist_common *)crdev->render_threads[i].cdev;
	    
    	    /* Close the file handles, but don't delete (unlink) the files */
	    thread_cdev->page_info.io_procs->fclose(thread_cdev->page_bfile, thread_cdev->page_bfname, false);
	    thread_cdev->page_info.io_procs->fclose(thread_cdev->page_cfile, thread_cdev->page_cfname, false);
	    thread_cdev->do_not_open_or_close_bandfiles = true;	/* we already closed the files */

	    gdev_prn_free_memory((gx_device *)thread_cdev);
	    gs_free_object(crdev->render_threads[i].memory, thread_cdev,
	    "clist_setup_render_threads");
	}
	if (crdev->render_threads[i].memory != NULL)
	    gs_memory_chunk_release(crdev->render_threads[i].memory); 
    }
    /* If we weren't able to create at least one thread, punt	*/
    /* Although a single thread isn't any more efficient, the	*/
    /* machinery still works, so that's OK.			*/
    if (i == 0) {
	if (crdev->render_threads[0].memory != NULL)
	    gs_memory_chunk_release(crdev->render_threads[0].memory); 
	gs_free_object(mem, crdev->render_threads, "clist_setup_render_threads");
	crdev->render_threads = NULL;
	/* restore the file pointers */
	if (cdev->page_cfile == NULL) {
	    char fmode[4];

	    strcpy(fmode, "a+");	/* file already exists and we want to re-use it */
	    strncat(fmode, gp_fmode_binary_suffix, 1);
	    cdev->page_info.io_procs->fopen(cdev->page_cfname, fmode, &cdev->page_cfile,
				mem, cdev->bandlist_memory, true);
	    cdev->page_info.io_procs->fseek(cdev->page_cfile, 0, SEEK_SET, cdev->page_cfname);
	    cdev->page_info.io_procs->fopen(cdev->page_bfname, fmode, &cdev->page_bfile,
				mem, cdev->bandlist_memory, false);
	    cdev->page_info.io_procs->fseek(cdev->page_bfile, 0, SEEK_SET, cdev->page_bfname);
	}
	eprintf1("Rendering threads not started, code=%d.\n", code);
	return_error(code);
    }
    crdev->num_render_threads = i;
    crdev->curr_render_thread = 0;

    if(gs_debug[':'] != 0)
	dprintf1("%% Using %d rendering threads\n", i);

    return 0;
}
Exemple #4
0
/* Set up and start the render threads */
static int
clist_setup_render_threads(gx_device *dev, int y, gx_process_page_options_t *options)
{
    gx_device_printer *pdev = (gx_device_printer *)dev;
    gx_device_clist *cldev = (gx_device_clist *)dev;
    gx_device_clist_common *cdev = (gx_device_clist_common *)cldev;
    gx_device_clist_reader *crdev = &cldev->reader;
    gs_memory_t *mem = cdev->bandlist_memory;
    gs_memory_t *chunk_base_mem = mem->thread_safe_memory;
    gs_memory_status_t mem_status;
    int i, band;
    int code = 0;
    int band_count = cdev->nbands;
    int band_height = crdev->page_info.band_params.BandHeight;
    crdev->num_render_threads = pdev->num_render_threads_requested;

    if(gs_debug[':'] != 0)
        dmprintf1(mem, "%% %d rendering threads requested.\n", pdev->num_render_threads_requested);

    if (crdev->num_render_threads > band_count)
        crdev->num_render_threads = band_count; /* don't bother starting more threads than bands */

    /* Allocate and initialize an array of thread control structures */
    crdev->render_threads = (clist_render_thread_control_t *)
              gs_alloc_byte_array(mem, crdev->num_render_threads,
              sizeof(clist_render_thread_control_t), "clist_setup_render_threads" );
    /* fallback to non-threaded if allocation fails */
    if (crdev->render_threads == NULL) {
        emprintf(mem, " VMerror prevented threads from starting.\n");
        return_error(gs_error_VMerror);
    }

    memset(crdev->render_threads, 0, crdev->num_render_threads *
            sizeof(clist_render_thread_control_t));
    crdev->main_thread_data = cdev->data;               /* save data area */
    /* Based on the line number requested, decide the order of band rendering */
    /* Almost all devices go in increasing line order (except the bmp* devices ) */
    crdev->thread_lookahead_direction = (y < (cdev->height - 1)) ? 1 : -1;
    band = y / band_height;

    /* If the 'mem' is not thread safe, we need to wrap it in a locking memory */
    gs_memory_status(chunk_base_mem, &mem_status);
    if (mem_status.is_thread_safe == false) {
            return_error(gs_error_VMerror);
    }

    /* If we don't have one large enough already, create an icc cache list */
    if (crdev->num_render_threads > crdev->icc_cache_list_len) {
        void *old = crdev->icc_cache_list;
        crdev->icc_cache_list = gs_alloc_byte_array(mem->thread_safe_memory,
                                    crdev->num_render_threads,
                                    sizeof(void*), "clist_render_setup_threads");
        if (crdev->icc_cache_list == NULL) {
            crdev->icc_cache_list = NULL;
            return_error(gs_error_VMerror);
        }
        if (crdev->icc_cache_list_len > 0)
            memcpy(crdev->icc_cache_list, old, sizeof(void *)*crdev->icc_cache_list_len);
        memset(&crdev->icc_cache_list[crdev->icc_cache_list_len], 0,
            (crdev->num_render_threads - crdev->icc_cache_list_len) * sizeof(void *));
        crdev->icc_cache_list_len = crdev->num_render_threads;
        gs_free_object(mem, old, "clist_render_setup_threads");
    }

    /* Loop creating the devices and semaphores for each thread, then start them */
    for (i=0; (i < crdev->num_render_threads) && (band >= 0) && (band < band_count);
            i++, band += crdev->thread_lookahead_direction) {
        gx_device *ndev;
        clist_render_thread_control_t *thread = &(crdev->render_threads[i]);

        ndev = setup_device_and_mem_for_thread(chunk_base_mem, dev, false, &crdev->icc_cache_list[i]);
        if (ndev == NULL) {
            code = gs_error_VMerror;	/* set code to an error for cleanup after the loop */
            break;
        }

        thread->cdev = ndev;
        thread->memory = ndev->memory;
        thread->band = -1;              /* a value that won't match any valid band */
        thread->options = options;
        thread->buffer = NULL;
        if (options && options->init_buffer_fn) {
            code = options->init_buffer_fn(options->arg, dev, thread->memory, dev->width, band_height, &thread->buffer);
            if (code < 0)
                break;
        }

        /* create the buf device for this thread, and allocate the semaphores */
        if ((code = gdev_create_buf_device(cdev->buf_procs.create_buf_device,
                                &(thread->bdev), ndev,
                                band*crdev->page_band_height, NULL,
                                thread->memory, &(crdev->color_usage_array[0])) < 0))
            break;
        if ((thread->sema_this = gx_semaphore_alloc(thread->memory)) == NULL ||
            (thread->sema_group = gx_semaphore_alloc(thread->memory)) == NULL) {
            code = gs_error_VMerror;
            break;
        }
        /* Start thread 'i' to do band */
        if ((code = clist_start_render_thread(dev, i, band)) < 0)
            break;
    }
    /* If the code < 0, the last thread creation failed -- clean it up */
    if (code < 0) {
        band -= crdev->thread_lookahead_direction;	/* update for 'next_band' usage */
        /* the following relies on 'free' ignoring NULL pointers */
        gx_semaphore_free(crdev->render_threads[i].sema_group);
        gx_semaphore_free(crdev->render_threads[i].sema_this);
        if (crdev->render_threads[i].bdev != NULL)
            cdev->buf_procs.destroy_buf_device(crdev->render_threads[i].bdev);
        if (crdev->render_threads[i].cdev != NULL) {
            gx_device_clist_common *thread_cdev = (gx_device_clist_common *)crdev->render_threads[i].cdev;

            /* Close the file handles, but don't delete (unlink) the files */
            thread_cdev->page_info.io_procs->fclose(thread_cdev->page_info.bfile, thread_cdev->page_info.bfname, false);
            thread_cdev->page_info.io_procs->fclose(thread_cdev->page_info.cfile, thread_cdev->page_info.cfname, false);
            thread_cdev->do_not_open_or_close_bandfiles = true; /* we already closed the files */

            gdev_prn_free_memory((gx_device *)thread_cdev);
            gs_free_object(crdev->render_threads[i].memory, thread_cdev,
            "clist_setup_render_threads");
        }
        if (crdev->render_threads[i].buffer != NULL && options && options->free_buffer_fn != NULL) {
            options->free_buffer_fn(options->arg, dev, crdev->render_threads[i].memory, crdev->render_threads[i].buffer);
            crdev->render_threads[i].buffer = NULL;
        }
        if (crdev->render_threads[i].memory != NULL) {
            gs_memory_chunk_release(crdev->render_threads[i].memory);
            crdev->render_threads[i].memory = NULL;
        }
    }
    /* If we weren't able to create at least one thread, punt   */
    /* Although a single thread isn't any more efficient, the   */
    /* machinery still works, so that's OK.                     */
    if (i == 0) {
        if (crdev->render_threads[0].memory != NULL) {
            gs_memory_chunk_release(crdev->render_threads[0].memory);
            /* free up the locking wrapper if we allocated one */
            if (chunk_base_mem != mem) {
                gs_memory_locked_release((gs_memory_locked_t *)chunk_base_mem);
                gs_free_object(mem, chunk_base_mem, "clist_setup_render_threads(locked allocator)");
            }
        }
        gs_free_object(mem, crdev->render_threads, "clist_setup_render_threads");
        crdev->render_threads = NULL;
        /* restore the file pointers */
        if (cdev->page_info.cfile == NULL) {
            char fmode[4];

            strcpy(fmode, "a+");        /* file already exists and we want to re-use it */
            strncat(fmode, gp_fmode_binary_suffix, 1);
            cdev->page_info.io_procs->fopen(cdev->page_info.cfname, fmode, &cdev->page_info.cfile,
                                mem, cdev->bandlist_memory, true);
            cdev->page_info.io_procs->fseek(cdev->page_info.cfile, 0, SEEK_SET, cdev->page_info.cfname);
            cdev->page_info.io_procs->fopen(cdev->page_info.bfname, fmode, &cdev->page_info.bfile,
                                mem, cdev->bandlist_memory, false);
            cdev->page_info.io_procs->fseek(cdev->page_info.bfile, 0, SEEK_SET, cdev->page_info.bfname);
        }
        emprintf1(mem, "Rendering threads not started, code=%d.\n", code);
        return_error(code);
    }
    crdev->num_render_threads = i;
    crdev->curr_render_thread = 0;
        crdev->next_band = band;

    if(gs_debug[':'] != 0)
        dmprintf1(mem, "%% Using %d rendering threads\n", i);

    return 0;
}