aboutsummaryrefslogtreecommitdiff
path: root/xpdf/TileCache.cc
diff options
context:
space:
mode:
Diffstat (limited to 'xpdf/TileCache.cc')
-rw-r--r--xpdf/TileCache.cc562
1 files changed, 562 insertions, 0 deletions
diff --git a/xpdf/TileCache.cc b/xpdf/TileCache.cc
new file mode 100644
index 0000000..ff002ed
--- /dev/null
+++ b/xpdf/TileCache.cc
@@ -0,0 +1,562 @@
+//========================================================================
+//
+// TileCache.cc
+//
+// Copyright 2014 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "gmem.h"
+#include "gmempp.h"
+#include "GList.h"
+#include "GMutex.h"
+#ifdef _WIN32
+# include <windows.h>
+#else
+# include <pthread.h>
+# include <unistd.h>
+#endif
+#include "Object.h"
+#include "PDFDoc.h"
+#include "SplashBitmap.h"
+#include "SplashOutputDev.h"
+#include "DisplayState.h"
+#include "GfxState.h"
+#include "TileMap.h"
+#include "TileCache.h"
+
+//------------------------------------------------------------------------
+// CachedTileDesc
+//------------------------------------------------------------------------
+
+enum CachedTileState {
+ cachedTileUnstarted, // worker thread hasn't started
+ // rasterization yet
+ cachedTileStarted, // worker thread is rasterizing the tile
+ cachedTileFinished, // rasterization is done
+ cachedTileCanceled // worker thread should stop rasterizing
+ // and remove this tile from the cache
+};
+
+class CachedTileDesc: public TileDesc {
+public:
+
+ CachedTileDesc(TileDesc *tile):
+ TileDesc(tile->page, tile->rotate, tile->dpi,
+ tile->tx, tile->ty, tile->tw, tile->th),
+ state(cachedTileUnstarted), active(gTrue),
+ bitmap(NULL), freeBitmap(gFalse) {}
+ ~CachedTileDesc();
+
+ CachedTileState state;
+ GBool active;
+ SplashBitmap *bitmap;
+ GBool freeBitmap;
+};
+
+CachedTileDesc::~CachedTileDesc() {
+ if (freeBitmap) {
+ delete bitmap;
+ }
+}
+
+//------------------------------------------------------------------------
+// OS-dependent threading support code
+//
+// NB: This wrapper code is not meant to be general purpose. Pthreads
+// condition objects are not equivalent to Windows event objects, in
+// general.
+//------------------------------------------------------------------------
+
+//-------------------- Windows --------------------
+#ifdef _WIN32
+
+typedef HANDLE GThreadID;
+typedef DWORD (WINAPI *GThreadFunc)(void *);
+#define GThreadReturn DWORD WINAPI
+
+static void gCreateThread(GThreadID *thr, GThreadFunc threadFunc,
+ void *data) {
+ *thr = CreateThread(NULL, 0, threadFunc, data, 0, NULL);
+}
+
+static void gJoinThread(GThreadID thr) {
+ WaitForSingleObject(thr, INFINITE);
+ CloseHandle(thr);
+}
+
+typedef HANDLE GCondition;
+
+static void gInitCondition(GCondition *c) {
+ *c = CreateEvent(NULL, TRUE, FALSE, NULL);
+}
+
+static void gDestroyCondition(GCondition *c) {
+ CloseHandle(*c);
+}
+
+static void gSignalCondition(GCondition *c) {
+ SetEvent(*c);
+}
+
+static void gClearCondition(GCondition *c) {
+ ResetEvent(*c);
+}
+
+static void gWaitCondition(GCondition *c, GMutex *m) {
+ LeaveCriticalSection(m);
+ WaitForSingleObject(*c, INFINITE);
+ EnterCriticalSection(m);
+}
+
+//-------------------- pthreads --------------------
+#else
+
+typedef pthread_t GThreadID;
+typedef void *(*GThreadFunc)(void *);
+#define GThreadReturn void*
+
+static void gCreateThread(GThreadID *thr, GThreadFunc threadFunc,
+ void *data) {
+ pthread_create(thr, NULL, threadFunc, data);
+}
+
+static void gJoinThread(GThreadID thr) {
+ pthread_join(thr, NULL);
+}
+
+typedef pthread_cond_t GCondition;
+
+static void gInitCondition(GCondition *c) {
+ pthread_cond_init(c, NULL);
+}
+
+static void gDestroyCondition(GCondition *c) {
+ pthread_cond_destroy(c);
+}
+
+static void gSignalCondition(GCondition *c) {
+ pthread_cond_broadcast(c);
+}
+
+static void gClearCondition(GCondition *c) {
+}
+
+static void gWaitCondition(GCondition *c, GMutex *m) {
+ pthread_cond_wait(c, m);
+}
+
+#endif
+
+//------------------------------------------------------------------------
+// TileCacheThreadPool
+//------------------------------------------------------------------------
+
+class TileCacheThreadPool {
+public:
+
+ TileCacheThreadPool(TileCache *tileCacheA, int nThreadsA);
+ ~TileCacheThreadPool();
+
+ // Called by the client when one or more jobs have been added to the
+ // queue. This must be called with the mutex unlocked.
+ void jobAdded();
+
+ // Wait for at least one job to be finished. This must be called
+ // with the mutex locked.
+ void waitForFinishedJob();
+
+ // The client should use the mutex to protect the queue data
+ // structure.
+ void lockMutex() { gLockMutex(&mutex); }
+ void unlockMutex() { gUnlockMutex(&mutex); }
+
+private:
+
+ static GThreadReturn threadFunc(void *arg);
+ void worker();
+
+ TileCache *tileCache;
+ int nThreads;
+ GThreadID *threads;
+ GBool quit;
+ GMutex mutex;
+ GCondition cond; // signalled when a job is added to the
+ // queue and when the quit flag is set
+ GCondition finishCond; // signalled whenever a worker thread
+ // finishes rasterizing a tile
+};
+
+TileCacheThreadPool::TileCacheThreadPool(TileCache *tileCacheA,
+ int nThreadsA) {
+ int i;
+
+ tileCache = tileCacheA;
+ nThreads = nThreadsA;
+ quit = gFalse;
+ gInitMutex(&mutex);
+ gInitCondition(&cond);
+ gInitCondition(&finishCond);
+ threads = (GThreadID *)gmallocn(nThreads, sizeof(GThreadID));
+ for (i = 0; i < nThreads; ++i) {
+ gCreateThread(&threads[i], &threadFunc, this);
+ }
+}
+
+TileCacheThreadPool::~TileCacheThreadPool() {
+ int i;
+
+ gLockMutex(&mutex);
+ quit = true;
+ gSignalCondition(&cond);
+ gUnlockMutex(&mutex);
+ for (i = 0; i < nThreads; ++i) {
+ gJoinThread(threads[i]);
+ }
+ gDestroyCondition(&cond);
+ gDestroyCondition(&finishCond);
+ gDestroyMutex(&mutex);
+ gfree(threads);
+}
+
+void TileCacheThreadPool::jobAdded() {
+ gLockMutex(&mutex);
+ gSignalCondition(&cond);
+ gUnlockMutex(&mutex);
+}
+
+GThreadReturn TileCacheThreadPool::threadFunc(void *arg) {
+ ((TileCacheThreadPool *)arg)->worker();
+ return 0;
+}
+
+void TileCacheThreadPool::worker() {
+ CachedTileDesc *ct;
+
+ while (1) {
+ gLockMutex(&mutex);
+ while (!quit && !(ct = tileCache->getUnstartedTile())) {
+ gWaitCondition(&cond, &mutex);
+ }
+ if (quit) {
+ gUnlockMutex(&mutex);
+ break;
+ }
+ if (!tileCache->hasUnstartedTiles()) {
+ gClearCondition(&cond);
+ }
+ gUnlockMutex(&mutex);
+ tileCache->rasterizeTile(ct);
+ gSignalCondition(&finishCond);
+ }
+}
+
+void TileCacheThreadPool::waitForFinishedJob() {
+ gWaitCondition(&finishCond, &mutex);
+}
+
+//------------------------------------------------------------------------
+// TileCache
+//------------------------------------------------------------------------
+
+TileCache::TileCache(DisplayState *stateA) {
+ state = stateA;
+ state->setTileCache(this);
+ cache = new GList();
+ threadPool = new TileCacheThreadPool(this, state->getNWorkerThreads());
+ tileDoneCbk = NULL;
+ tileDoneCbkData = NULL;
+}
+
+TileCache::~TileCache() {
+ flushCache(gFalse);
+ delete threadPool;
+ delete cache;
+}
+
+void TileCache::setActiveTileList(GList *tiles) {
+ TileDesc *tile;
+ CachedTileDesc *ct;
+ int tileIdx, cacheIdx;
+ GBool newTiles;
+
+ threadPool->lockMutex();
+
+ // remove any unstarted tiles not on the new active list;
+ // cancel any started tiles not on the new active list;
+ // mark all other tiles as inactive (active tiles will be marked later)
+ cacheIdx = 0;
+ while (cacheIdx < cache->getLength()) {
+ ct = (CachedTileDesc *)cache->get(cacheIdx);
+ if (ct->state == cachedTileUnstarted && findTile(ct, tiles) < 0) {
+ delete (CachedTileDesc *)cache->del(cacheIdx);
+ } else if (ct->state == cachedTileStarted && findTile(ct, tiles) < 0) {
+ ct->state = cachedTileCanceled;
+ ++cacheIdx;
+ } else {
+ ct->active = gFalse;
+ ++cacheIdx;
+ }
+ }
+
+ // mark cached tiles as active; add any new tiles to the cache
+ newTiles = gFalse;
+ for (tileIdx = 0; tileIdx < tiles->getLength(); ++tileIdx) {
+ tile = (TileDesc *)tiles->get(tileIdx);
+ cacheIdx = findTile(tile, cache);
+ if (cacheIdx >= 0) {
+ ct = (CachedTileDesc *)cache->del(cacheIdx);
+ } else {
+ ct = new CachedTileDesc(tile);
+ newTiles = gTrue;
+ }
+ ct->active = gTrue;
+ cache->insert(0, ct);
+ }
+
+ cleanCache();
+
+ threadPool->unlockMutex();
+
+ if (newTiles) {
+ threadPool->jobAdded();
+ }
+}
+
+SplashBitmap *TileCache::getTileBitmap(TileDesc *tile, GBool *finished) {
+ CachedTileDesc *ct;
+ SplashBitmap *bitmap;
+ int cacheIdx;
+
+ threadPool->lockMutex();
+ cacheIdx = findTile(tile, cache);
+ if (cacheIdx < 0) {
+ threadPool->unlockMutex();
+ return NULL;
+ }
+ ct = (CachedTileDesc *)cache->get(cacheIdx);
+ if (ct->state != cachedTileCanceled) {
+ bitmap = ct->bitmap;
+ } else {
+ bitmap = NULL;
+ }
+ if (finished) {
+ *finished = ct->state == cachedTileFinished;
+ }
+ threadPool->unlockMutex();
+ return bitmap;
+}
+
+void TileCache::paperColorChanged() {
+ flushCache(gFalse);
+}
+
+void TileCache::reverseVideoChanged() {
+ flushCache(gFalse);
+}
+
+void TileCache::optionalContentChanged() {
+ flushCache(gFalse);
+}
+
+void TileCache::docChanged() {
+ flushCache(gTrue);
+}
+
+
+void TileCache::forceRedraw() {
+ flushCache(gFalse);
+}
+
+// Search for <tile> on <tileList>, and return its index if found, or
+// -1 if not found. If <tile> is part of the cache, or if <tileList>
+// is the cache, the caller must have locked the ThreadPool mutex.
+int TileCache::findTile(TileDesc *tile, GList *tileList) {
+ TileDesc *t;
+ int i;
+
+ for (i = 0; i < tileList->getLength(); ++i) {
+ t = (TileDesc *)tileList->get(i);
+ if (t->matches(tile)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+// If there are too many tiles in the cache, remove the least recently
+// used tiles. Never removes active tiles. The caller must have
+// locked the ThreadPool mutex.
+void TileCache::cleanCache() {
+ CachedTileDesc *ct;
+ int n, i;
+
+ // count the number of non-canceled tiles
+ n = 0;
+ for (i = 0; i < cache->getLength(); ++i) {
+ ct = (CachedTileDesc *)cache->get(i);
+ if (ct->state != cachedTileCanceled) {
+ ++n;
+ }
+ }
+
+ // if there are too many non-canceled tiles, remove tiles
+ i = cache->getLength() - 1;
+ while (n > state->getTileCacheSize() && i >= 0) {
+ ct = (CachedTileDesc *)cache->get(i);
+ if (ct->active) {
+ break;
+ }
+ // any non-active tiles with state == cachedTileUnstarted should
+ // already have been removed by setActiveTileList()
+ if (ct->state == cachedTileFinished) {
+ delete (CachedTileDesc *)cache->del(i);
+ --n;
+ --i;
+ } else {
+ --i;
+ }
+ }
+}
+
+// Remove all cached tiles. For tiles that are being rasterized, sets
+// state to canceled. If <wait> is true, this function won't return
+// until the cache is empty, i.e., until all possible users of the
+// PDFDoc are done.
+void TileCache::flushCache(GBool wait) {
+ CachedTileDesc *ct;
+ int i;
+
+ threadPool->lockMutex();
+ i = 0;
+ while (i < cache->getLength()) {
+ ct = (CachedTileDesc *)cache->get(i);
+ switch (ct->state) {
+ case cachedTileUnstarted:
+ case cachedTileFinished:
+ delete (CachedTileDesc *)cache->del(i);
+ break;
+ case cachedTileStarted:
+ ct->state = cachedTileCanceled;
+ ++i;
+ break;
+ case cachedTileCanceled:
+ default:
+ ++i;
+ break;
+ }
+ }
+ if (wait) {
+ while (cache->getLength() > 0) {
+ threadPool->waitForFinishedJob();
+ }
+ }
+ threadPool->unlockMutex();
+}
+
+// Remove a tile from the cache and delete it. This will be called
+// with the TileCacheThreadPool mutex locked.
+void TileCache::removeTile(CachedTileDesc *ct) {
+ int i;
+
+ for (i = 0; i < cache->getLength(); ++i) {
+ if (cache->get(i) == ct) {
+ delete (CachedTileDesc *)cache->del(i);
+ break;
+ }
+ }
+}
+
+// Return true if there are one or more unstarted tiles. This will be
+// called with the TileCacheThreadPool mutex locked.
+GBool TileCache::hasUnstartedTiles() {
+ CachedTileDesc *ct;
+ int i;
+
+ for (i = 0; i < cache->getLength(); ++i) {
+ ct = (CachedTileDesc *)cache->get(i);
+ if (ct->state == cachedTileUnstarted) {
+ return gTrue;
+ }
+ }
+ return gFalse;
+}
+
+// Return the next unstarted tile, changing its state to
+// cachedTileStarted. If there are no unstarted tiles, return NULL.
+// This will be called with the TileCacheThreadPool mutex locked.
+CachedTileDesc *TileCache::getUnstartedTile() {
+ CachedTileDesc *ct;
+ int i;
+
+ for (i = 0; i < cache->getLength(); ++i) {
+ ct = (CachedTileDesc *)cache->get(i);
+ if (ct->state == cachedTileUnstarted) {
+ ct->state = cachedTileStarted;
+ return ct;
+ }
+ }
+ return NULL;
+}
+
+struct TileCacheStartPageInfo {
+ TileCache *tileCache;
+ CachedTileDesc *ct;
+ SplashOutputDev *out;
+};
+
+void TileCache::startPageCbk(void *data) {
+ TileCacheStartPageInfo *info = (TileCacheStartPageInfo *)data;
+
+ info->tileCache->threadPool->lockMutex();
+ info->ct->bitmap = info->out->getBitmap();
+ info->ct->freeBitmap = gFalse;
+ info->tileCache->threadPool->unlockMutex();
+}
+
+// Rasterize <tile>. The state has already been set to
+// cachedTileStarted.
+void TileCache::rasterizeTile(CachedTileDesc *ct) {
+ SplashOutputDev *out;
+ TileCacheStartPageInfo info;
+
+
+ out = new SplashOutputDev(state->getColorMode(), 1, state->getReverseVideo(),
+ state->getPaperColor());
+ info.tileCache = this;
+ info.ct = ct;
+ info.out = out;
+ out->setStartPageCallback(&TileCache::startPageCbk, &info);
+ out->startDoc(state->getDoc()->getXRef());
+ state->getDoc()->displayPageSlice(out, ct->page, ct->dpi, ct->dpi, ct->rotate,
+ gFalse, gTrue, gFalse,
+ ct->tx, ct->ty, ct->tw, ct->th,
+ &abortCheckCbk, ct);
+ if (ct->state == cachedTileCanceled) {
+ threadPool->lockMutex();
+ removeTile(ct);
+ threadPool->unlockMutex();
+ } else {
+ threadPool->lockMutex();
+ ct->bitmap = out->takeBitmap();
+ ct->freeBitmap = gTrue;
+ ct->state = cachedTileFinished;
+ threadPool->unlockMutex();
+ if (tileDoneCbk) {
+ (*tileDoneCbk)(tileDoneCbkData);
+ }
+ }
+ delete out;
+}
+
+
+GBool TileCache::abortCheckCbk(void *data) {
+ CachedTileDesc *ct = (CachedTileDesc *)data;
+ return ct->state == cachedTileCanceled;
+}