diff options
author | Tor Andersson <tor.andersson@artifex.com> | 2017-04-07 16:16:56 +0200 |
---|---|---|
committer | Tor Andersson <tor.andersson@artifex.com> | 2017-04-13 13:16:21 +0200 |
commit | 9c805cc9f934cd12e89014db8ad70e3191cdaf2d (patch) | |
tree | 5aece10eaccc25d060b2803c4d201341048caf96 /docs/examples/multi-threaded.c | |
parent | 49d5a9856020627af1fc4063ff5a26b26e9270cf (diff) | |
download | mupdf-9c805cc9f934cd12e89014db8ad70e3191cdaf2d.tar.xz |
Move all examples to docs/examples directory.
Diffstat (limited to 'docs/examples/multi-threaded.c')
-rw-r--r-- | docs/examples/multi-threaded.c | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/docs/examples/multi-threaded.c b/docs/examples/multi-threaded.c new file mode 100644 index 00000000..15fdcba2 --- /dev/null +++ b/docs/examples/multi-threaded.c @@ -0,0 +1,289 @@ +// Multi-threaded rendering of all pages in a document to PNG images. + +// First look at doc/example.c and make sure you understand it. +// Then read the multi-threading section in doc/overview.txt, +// before coming back here to see an example of multi-threading. + +// This example will create one main thread for reading pages from the +// document, and one thread per page for rendering. After rendering +// the main thread will wait for each rendering thread to complete before +// writing that thread's rendered image to a PNG image. There is +// nothing in MuPDF requiring a rendering thread to only render a +// single page, this is just a design decision taken for this example. + +// Compile a debug build of mupdf, then compile and run this example: +// +// gcc -g -o build/debug/example-mt -Iinclude docs/multi-threaded.c \ +// build/debug/libmupdf.a \ +// build/debug/libmupdfthird.a \ +// -lpthread -lcrypto -lm +// +// build/debug/example-mt /path/to/document.pdf +// +// Caution! As all pages are rendered simultaneously, please choose a +// file with just a few pages to avoid stressing your machine too +// much. Also you may run in to a limitation on the number of threads +// depending on your environment. + +// Include the MuPDF header file, and pthread's header file. +#include <mupdf/fitz.h> +#include <pthread.h> + +// A convenience function for dying abruptly on pthread errors. + +void +fail(char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +// The data structure passed between the requesting main thread and +// each rendering thread. + +struct data { + // A pointer to the original context in the main thread sent + // from main to rendering thread. It will be used to create + // each rendering thread's context clone. + fz_context *ctx; + + // Page number sent from main to rendering thread for printing + int pagenumber; + + // The display list as obtained by the main thread and sent + // from main to rendering thread. This contains the drawing + // commands (text, images, etc.) for the page that should be + // rendered. + fz_display_list *list; + + // The area of the page to render as obtained by the main + // thread and sent from main to rendering thread. + fz_rect bbox; + + // This is the result, a pixmap containing the rendered page. + // It is passed first from main thread to the rendering + // thread, then its samples are changed by the rendering + // thread, and then back from the rendering thread to the main + // thread. + fz_pixmap *pix; +}; + +// This is the function run by each rendering function. It takes +// pointer to an instance of the data structure described above and +// renders the display list into the pixmap before exiting. + +void * +renderer(void *data) +{ + int pagenumber = ((struct data *) data)->pagenumber; + fz_context *ctx = ((struct data *) data)->ctx; + fz_display_list *list = ((struct data *) data)->list; + fz_rect bbox = ((struct data *) data)->bbox; + fz_pixmap *pix = ((struct data *) data)->pix; + fz_device *dev; + + fprintf(stderr, "thread at page %d loading!\n", pagenumber); + + // The context pointer is pointing to the main thread's + // context, so here we create a new context based on it for + // use in this thread. + + ctx = fz_clone_context(ctx); + + // Next we run the display list through the draw device which + // will render the request area of the page to the pixmap. + + fprintf(stderr, "thread at page %d rendering!\n", pagenumber); + dev = fz_new_draw_device(ctx, &fz_identity, pix); + fz_run_display_list(ctx, list, dev, &fz_identity, &bbox, NULL); + fz_close_device(ctx, dev); + fz_drop_device(ctx, dev); + + // This threads context is freed. + + fz_drop_context(ctx); + + fprintf(stderr, "thread at page %d done!\n", pagenumber); + + return data; +} + +// These are the two locking functions required by MuPDF when +// operating in a multi-threaded environment. They each take a user +// argument that can be used to transfer some state, in this case a +// pointer to the array of mutexes. + +void lock_mutex(void *user, int lock) +{ + pthread_mutex_t *mutex = (pthread_mutex_t *) user; + + if (pthread_mutex_lock(&mutex[lock]) != 0) + fail("pthread_mutex_lock()"); +} + +void unlock_mutex(void *user, int lock) +{ + pthread_mutex_t *mutex = (pthread_mutex_t *) user; + + if (pthread_mutex_unlock(&mutex[lock]) != 0) + fail("pthread_mutex_unlock()"); +} + +int main(int argc, char **argv) +{ + char *filename = argc >= 2 ? argv[1] : ""; + pthread_t *thread = NULL; + fz_locks_context locks; + pthread_mutex_t mutex[FZ_LOCK_MAX]; + fz_context *ctx; + fz_document *doc; + int threads; + int i; + + // Initialize FZ_LOCK_MAX number of non-recursive mutexes. + + for (i = 0; i < FZ_LOCK_MAX; i++) + { + if (pthread_mutex_init(&mutex[i], NULL) != 0) + fail("pthread_mutex_init()"); + } + + // Initialize the locking structure with function pointers to + // the locking functions and to the user data. In this case + // the user data is a pointer to the array of mutexes so the + // locking functions can find the relevant lock to change when + // they are called. This way we avoid global variables. + + locks.user = mutex; + locks.lock = lock_mutex; + locks.unlock = unlock_mutex; + + // This is the main threads context function, so supply the + // locking structure. This context will be used to parse all + // the pages from the document. + + ctx = fz_new_context(NULL, &locks, FZ_STORE_UNLIMITED); + + // Register default file types. + + fz_register_document_handlers(ctx); + + // Open the PDF, XPS or CBZ document. Note, this binds doc to ctx. + // You must only ever use doc with ctx - never a clone of it! + + doc = fz_open_document(ctx, filename); + + // Retrieve the number of pages, which translates to the + // number of threads used for rendering pages. + + threads = fz_count_pages(ctx, doc); + fprintf(stderr, "spawning %d threads, one per page...\n", threads); + + thread = malloc(threads * sizeof (pthread_t)); + + for (i = 0; i < threads; i++) + { + fz_page *page; + fz_rect bbox; + fz_irect rbox; + fz_display_list *list; + fz_device *dev; + fz_pixmap *pix; + struct data *data; + + // Load the relevant page for each thread. Note, that this + // cannot be done on the worker threads, as each use of doc + // uses ctx, and only one thread can be using ctx at a time. + + page = fz_load_page(ctx, doc, i); + + // Compute the bounding box for each page. + + fz_bound_page(ctx, page, &bbox); + + // Create a display list that will hold the drawing + // commands for the page. Once we have the display list + // this can safely be used on any other thread as it is + // not bound to a given context. + + list = fz_new_display_list(ctx, &bbox); + + // Run the loaded page through a display list device + // to populate the page's display list. + + dev = fz_new_list_device(ctx, list); + fz_run_page(ctx, page, dev, &fz_identity, NULL); + fz_close_device(ctx, dev); + fz_drop_device(ctx, dev); + + // The page is no longer needed, all drawing commands + // are now in the display list. + + fz_drop_page(ctx, page); + + // Create a white pixmap using the correct dimensions. + + pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), fz_round_rect(&rbox, &bbox), 0); + fz_clear_pixmap_with_value(ctx, pix, 0xff); + + // Populate the data structure to be sent to the + // rendering thread for this page. + + data = malloc(sizeof (struct data)); + + data->pagenumber = i + 1; + data->ctx = ctx; + data->list = list; + data->bbox = bbox; + data->pix = pix; + + // Create the thread and pass it the data structure. + + if (pthread_create(&thread[i], NULL, renderer, data) != 0) + fail("pthread_create()"); + } + + // Now each thread is rendering pages, so wait for each thread + // to complete its rendering. + + fprintf(stderr, "joining %d threads...\n", threads); + for (i = threads - 1; i >= 0; i--) + { + char filename[42]; + struct data *data; + + if (pthread_join(thread[i], (void **) &data) != 0) + fail("pthread_join"); + + sprintf(filename, "out%04d.png", i); + fprintf(stderr, "\tSaving %s...\n", filename); + + // Write the rendered image to a PNG file + + fz_save_pixmap_as_png(ctx, data->pix, filename); + + // Free the thread's pixmap and display list since + // they were allocated by the main thread above. + + fz_drop_pixmap(ctx, data->pix); + fz_drop_display_list(ctx, data->list); + + // Free the data structured passed back and forth + // between the main thread and rendering thread. + + free(data); + } + + fprintf(stderr, "finally!\n"); + fflush(NULL); + + free(thread); + + // Finally the document is closed and the main thread's + // context is freed. + + fz_drop_document(ctx, doc); + fz_drop_context(ctx); + + return 0; +} |