1 //========================================================================
2 //
3 // TileCache.cc
4 //
5 // Copyright 2014 Glyph & Cog, LLC
6 //
7 //========================================================================
8 
9 #include <aconf.h>
10 
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14 
15 #include "gmem.h"
16 #include "gmempp.h"
17 #include "GList.h"
18 #include "GMutex.h"
19 #ifdef _WIN32
20 #  include <windows.h>
21 #else
22 #  include <pthread.h>
23 #  include <unistd.h>
24 #endif
25 #include "Object.h"
26 #include "PDFDoc.h"
27 #include "SplashBitmap.h"
28 #include "SplashOutputDev.h"
29 #include "DisplayState.h"
30 #include "GfxState.h"
31 #include "TileMap.h"
32 #include "TileCache.h"
33 
34 //------------------------------------------------------------------------
35 // CachedTileDesc
36 //------------------------------------------------------------------------
37 
38 enum CachedTileState {
39   cachedTileUnstarted,		// worker thread hasn't started
40 				//   rasterization yet
41   cachedTileStarted,		// worker thread is rasterizing the tile
42   cachedTileFinished,		// rasterization is done
43   cachedTileCanceled		// worker thread should stop rasterizing
44 				//   and remove this tile from the cache
45 };
46 
47 class CachedTileDesc: public TileDesc {
48 public:
49 
CachedTileDesc(TileDesc * tile)50   CachedTileDesc(TileDesc *tile):
51     TileDesc(tile->page, tile->rotate, tile->dpi,
52 	     tile->tx, tile->ty, tile->tw, tile->th),
53     state(cachedTileUnstarted), active(gTrue),
54     bitmap(NULL), freeBitmap(gFalse) {}
55   ~CachedTileDesc();
56 
57   CachedTileState state;
58   GBool active;
59   SplashBitmap *bitmap;
60   GBool freeBitmap;
61 };
62 
~CachedTileDesc()63 CachedTileDesc::~CachedTileDesc() {
64   if (freeBitmap) {
65     delete bitmap;
66   }
67 }
68 
69 //------------------------------------------------------------------------
70 // OS-dependent threading support code
71 //
72 // NB: This wrapper code is not meant to be general purpose.  Pthreads
73 // condition objects are not equivalent to Windows event objects, in
74 // general.
75 //------------------------------------------------------------------------
76 
77 //-------------------- Windows --------------------
78 #ifdef _WIN32
79 
80 typedef HANDLE GThreadID;
81 typedef DWORD (WINAPI *GThreadFunc)(void *);
82 #define GThreadReturn DWORD WINAPI
83 
gCreateThread(GThreadID * thr,GThreadFunc threadFunc,void * data)84 static void gCreateThread(GThreadID *thr, GThreadFunc threadFunc,
85 			  void *data) {
86   *thr = CreateThread(NULL, 0, threadFunc, data, 0, NULL);
87 }
88 
gJoinThread(GThreadID thr)89 static void gJoinThread(GThreadID thr) {
90   WaitForSingleObject(thr, INFINITE);
91   CloseHandle(thr);
92 }
93 
94 typedef HANDLE GCondition;
95 
gInitCondition(GCondition * c)96 static void gInitCondition(GCondition *c) {
97   *c = CreateEvent(NULL, TRUE, FALSE, NULL);
98 }
99 
gDestroyCondition(GCondition * c)100 static void gDestroyCondition(GCondition *c) {
101   CloseHandle(*c);
102 }
103 
gSignalCondition(GCondition * c)104 static void gSignalCondition(GCondition *c) {
105   SetEvent(*c);
106 }
107 
gClearCondition(GCondition * c)108 static void gClearCondition(GCondition *c) {
109   ResetEvent(*c);
110 }
111 
gWaitCondition(GCondition * c,GMutex * m)112 static void gWaitCondition(GCondition *c, GMutex *m) {
113   LeaveCriticalSection(m);
114   WaitForSingleObject(*c, INFINITE);
115   EnterCriticalSection(m);
116 }
117 
118 //-------------------- pthreads --------------------
119 #else
120 
121 typedef pthread_t GThreadID;
122 typedef void *(*GThreadFunc)(void *);
123 #define GThreadReturn void*
124 
gCreateThread(GThreadID * thr,GThreadFunc threadFunc,void * data)125 static void gCreateThread(GThreadID *thr, GThreadFunc threadFunc,
126 			  void *data) {
127   pthread_create(thr, NULL, threadFunc, data);
128 }
129 
gJoinThread(GThreadID thr)130 static void gJoinThread(GThreadID thr) {
131   pthread_join(thr, NULL);
132 }
133 
134 typedef pthread_cond_t GCondition;
135 
gInitCondition(GCondition * c)136 static void gInitCondition(GCondition *c) {
137   pthread_cond_init(c, NULL);
138 }
139 
gDestroyCondition(GCondition * c)140 static void gDestroyCondition(GCondition *c) {
141   pthread_cond_destroy(c);
142 }
143 
gSignalCondition(GCondition * c)144 static void gSignalCondition(GCondition *c) {
145   pthread_cond_broadcast(c);
146 }
147 
gClearCondition(GCondition * c)148 static void gClearCondition(GCondition *c) {
149 }
150 
gWaitCondition(GCondition * c,GMutex * m)151 static void gWaitCondition(GCondition *c, GMutex *m) {
152   pthread_cond_wait(c, m);
153 }
154 
155 #endif
156 
157 //------------------------------------------------------------------------
158 // TileCacheThreadPool
159 //------------------------------------------------------------------------
160 
161 class TileCacheThreadPool {
162 public:
163 
164   TileCacheThreadPool(TileCache *tileCacheA, int nThreadsA);
165   ~TileCacheThreadPool();
166 
167   // Called by the client when one or more jobs have been added to the
168   // queue.  This must be called with the mutex unlocked.
169   void jobAdded();
170 
171   // Wait for at least one job to be finished.  This must be called
172   // with the mutex locked.
173   void waitForFinishedJob();
174 
175   // The client should use the mutex to protect the queue data
176   // structure.
lockMutex()177   void lockMutex() { gLockMutex(&mutex); }
unlockMutex()178   void unlockMutex() { gUnlockMutex(&mutex); }
179 
180 private:
181 
182   static GThreadReturn threadFunc(void *arg);
183   void worker();
184 
185   TileCache *tileCache;
186   int nThreads;
187   GThreadID *threads;
188   GBool quit;
189   GMutex mutex;
190   GCondition cond;		// signalled when a job is added to the
191 				//   queue and when the quit flag is set
192   GCondition finishCond;	// signalled whenever a worker thread
193 				//   finishes rasterizing a tile
194 };
195 
TileCacheThreadPool(TileCache * tileCacheA,int nThreadsA)196 TileCacheThreadPool::TileCacheThreadPool(TileCache *tileCacheA,
197 					 int nThreadsA) {
198   int i;
199 
200   tileCache = tileCacheA;
201   nThreads = nThreadsA;
202   quit = gFalse;
203   gInitMutex(&mutex);
204   gInitCondition(&cond);
205   gInitCondition(&finishCond);
206   threads = (GThreadID *)gmallocn(nThreads, sizeof(GThreadID));
207   for (i = 0; i < nThreads; ++i) {
208     gCreateThread(&threads[i], &threadFunc, this);
209   }
210 }
211 
~TileCacheThreadPool()212 TileCacheThreadPool::~TileCacheThreadPool() {
213   int i;
214 
215   gLockMutex(&mutex);
216   quit = true;
217   gSignalCondition(&cond);
218   gUnlockMutex(&mutex);
219   for (i = 0; i < nThreads; ++i) {
220     gJoinThread(threads[i]);
221   }
222   gDestroyCondition(&cond);
223   gDestroyCondition(&finishCond);
224   gDestroyMutex(&mutex);
225   gfree(threads);
226 }
227 
jobAdded()228 void TileCacheThreadPool::jobAdded() {
229   gLockMutex(&mutex);
230   gSignalCondition(&cond);
231   gUnlockMutex(&mutex);
232 }
233 
threadFunc(void * arg)234 GThreadReturn TileCacheThreadPool::threadFunc(void *arg) {
235   ((TileCacheThreadPool *)arg)->worker();
236   return 0;
237 }
238 
worker()239 void TileCacheThreadPool::worker() {
240   CachedTileDesc *ct;
241 
242   while (1) {
243     gLockMutex(&mutex);
244     while (!quit && !(ct = tileCache->getUnstartedTile())) {
245       gWaitCondition(&cond, &mutex);
246     }
247     if (quit) {
248       gUnlockMutex(&mutex);
249       break;
250     }
251     if (!tileCache->hasUnstartedTiles()) {
252       gClearCondition(&cond);
253     }
254     gUnlockMutex(&mutex);
255     tileCache->rasterizeTile(ct);
256     gSignalCondition(&finishCond);
257   }
258 }
259 
waitForFinishedJob()260 void TileCacheThreadPool::waitForFinishedJob() {
261   gWaitCondition(&finishCond, &mutex);
262 }
263 
264 //------------------------------------------------------------------------
265 // TileCache
266 //------------------------------------------------------------------------
267 
TileCache(DisplayState * stateA)268 TileCache::TileCache(DisplayState *stateA) {
269   state = stateA;
270   state->setTileCache(this);
271   cache = new GList();
272   threadPool = new TileCacheThreadPool(this, state->getNWorkerThreads());
273   tileDoneCbk = NULL;
274   tileDoneCbkData = NULL;
275 }
276 
~TileCache()277 TileCache::~TileCache() {
278   flushCache(gFalse);
279   delete threadPool;
280   delete cache;
281 }
282 
setActiveTileList(GList * tiles)283 void TileCache::setActiveTileList(GList *tiles) {
284   TileDesc *tile;
285   CachedTileDesc *ct;
286   int tileIdx, cacheIdx;
287   GBool newTiles;
288 
289   threadPool->lockMutex();
290 
291   // remove any unstarted tiles not on the new active list;
292   // cancel any started tiles not on the new active list;
293   // mark all other tiles as inactive (active tiles will be marked later)
294   cacheIdx = 0;
295   while (cacheIdx < cache->getLength()) {
296     ct = (CachedTileDesc *)cache->get(cacheIdx);
297     if (ct->state == cachedTileUnstarted && findTile(ct, tiles) < 0) {
298       delete (CachedTileDesc *)cache->del(cacheIdx);
299     } else if (ct->state == cachedTileStarted && findTile(ct, tiles) < 0) {
300       ct->state = cachedTileCanceled;
301       ++cacheIdx;
302     } else {
303       ct->active = gFalse;
304       ++cacheIdx;
305     }
306   }
307 
308   // mark cached tiles as active; add any new tiles to the cache
309   newTiles = gFalse;
310   for (tileIdx = 0; tileIdx < tiles->getLength(); ++tileIdx) {
311     tile = (TileDesc *)tiles->get(tileIdx);
312     cacheIdx = findTile(tile, cache);
313     if (cacheIdx >= 0) {
314       ct = (CachedTileDesc *)cache->del(cacheIdx);
315     } else {
316       ct = new CachedTileDesc(tile);
317       newTiles = gTrue;
318     }
319     ct->active = gTrue;
320     cache->insert(0, ct);
321   }
322 
323   cleanCache();
324 
325   threadPool->unlockMutex();
326 
327   if (newTiles) {
328     threadPool->jobAdded();
329   }
330 }
331 
getTileBitmap(TileDesc * tile,GBool * finished)332 SplashBitmap *TileCache::getTileBitmap(TileDesc *tile, GBool *finished) {
333   CachedTileDesc *ct;
334   SplashBitmap *bitmap;
335   int cacheIdx;
336 
337   threadPool->lockMutex();
338   cacheIdx = findTile(tile, cache);
339   if (cacheIdx < 0) {
340     threadPool->unlockMutex();
341     return NULL;
342   }
343   ct = (CachedTileDesc *)cache->get(cacheIdx);
344   if (ct->state != cachedTileCanceled) {
345     bitmap = ct->bitmap;
346   } else {
347     bitmap = NULL;
348   }
349   if (finished) {
350     *finished = ct->state == cachedTileFinished;
351   }
352   threadPool->unlockMutex();
353   return bitmap;
354 }
355 
paperColorChanged()356 void TileCache::paperColorChanged() {
357   flushCache(gFalse);
358 }
359 
reverseVideoChanged()360 void TileCache::reverseVideoChanged() {
361   flushCache(gFalse);
362 }
363 
optionalContentChanged()364 void TileCache::optionalContentChanged() {
365   flushCache(gFalse);
366 }
367 
docChanged()368 void TileCache::docChanged() {
369   flushCache(gTrue);
370 }
371 
372 
forceRedraw()373 void TileCache::forceRedraw() {
374   flushCache(gFalse);
375 }
376 
377 // Search for <tile> on <tileList>, and return its index if found, or
378 // -1 if not found.  If <tile> is part of the cache, or if <tileList>
379 // is the cache, the caller must have locked the ThreadPool mutex.
findTile(TileDesc * tile,GList * tileList)380 int TileCache::findTile(TileDesc *tile, GList *tileList) {
381   TileDesc *t;
382   int i;
383 
384   for (i = 0; i < tileList->getLength(); ++i) {
385     t = (TileDesc *)tileList->get(i);
386     if (t->matches(tile)) {
387       return i;
388     }
389   }
390   return -1;
391 }
392 
393 // If there are too many tiles in the cache, remove the least recently
394 // used tiles.  Never removes active tiles.  The caller must have
395 // locked the ThreadPool mutex.
cleanCache()396 void TileCache::cleanCache() {
397   CachedTileDesc *ct;
398   int n, i;
399 
400   // count the number of non-canceled tiles
401   n = 0;
402   for (i = 0; i < cache->getLength(); ++i) {
403     ct = (CachedTileDesc *)cache->get(i);
404     if (ct->state != cachedTileCanceled) {
405       ++n;
406     }
407   }
408 
409   // if there are too many non-canceled tiles, remove tiles
410   i = cache->getLength() - 1;
411   while (n > state->getTileCacheSize() && i >= 0) {
412     ct = (CachedTileDesc *)cache->get(i);
413     if (ct->active) {
414       break;
415     }
416     // any non-active tiles with state == cachedTileUnstarted should
417     // already have been removed by setActiveTileList()
418     if (ct->state == cachedTileFinished) {
419       delete (CachedTileDesc *)cache->del(i);
420       --n;
421       --i;
422     } else {
423       --i;
424     }
425   }
426 }
427 
428 // Remove all cached tiles.  For tiles that are being rasterized, sets
429 // state to canceled.  If <wait> is true, this function won't return
430 // until the cache is empty, i.e., until all possible users of the
431 // PDFDoc are done.
flushCache(GBool wait)432 void TileCache::flushCache(GBool wait) {
433   CachedTileDesc *ct;
434   int i;
435 
436   threadPool->lockMutex();
437   i = 0;
438   while (i < cache->getLength()) {
439     ct = (CachedTileDesc *)cache->get(i);
440     switch (ct->state) {
441     case cachedTileUnstarted:
442     case cachedTileFinished:
443       delete (CachedTileDesc *)cache->del(i);
444       break;
445     case cachedTileStarted:
446       ct->state = cachedTileCanceled;
447       ++i;
448       break;
449     case cachedTileCanceled:
450     default:
451       ++i;
452       break;
453     }
454   }
455   if (wait) {
456     while (cache->getLength() > 0) {
457       threadPool->waitForFinishedJob();
458     }
459   }
460   threadPool->unlockMutex();
461 }
462 
463 // Remove a tile from the cache and delete it.  This will be called
464 // with the TileCacheThreadPool mutex locked.
removeTile(CachedTileDesc * ct)465 void TileCache::removeTile(CachedTileDesc *ct) {
466   int i;
467 
468   for (i = 0; i < cache->getLength(); ++i) {
469     if (cache->get(i) == ct) {
470       delete (CachedTileDesc *)cache->del(i);
471       break;
472     }
473   }
474 }
475 
476 // Return true if there are one or more unstarted tiles.  This will be
477 // called with the TileCacheThreadPool mutex locked.
hasUnstartedTiles()478 GBool TileCache::hasUnstartedTiles() {
479   CachedTileDesc *ct;
480   int i;
481 
482   for (i = 0; i < cache->getLength(); ++i) {
483     ct = (CachedTileDesc *)cache->get(i);
484     if (ct->state == cachedTileUnstarted) {
485       return gTrue;
486     }
487   }
488   return gFalse;
489 }
490 
491 // Return the next unstarted tile, changing its state to
492 // cachedTileStarted.  If there are no unstarted tiles, return NULL.
493 // This will be called with the TileCacheThreadPool mutex locked.
getUnstartedTile()494 CachedTileDesc *TileCache::getUnstartedTile() {
495   CachedTileDesc *ct;
496   int i;
497 
498   for (i = 0; i < cache->getLength(); ++i) {
499     ct = (CachedTileDesc *)cache->get(i);
500     if (ct->state == cachedTileUnstarted) {
501       ct->state = cachedTileStarted;
502       return ct;
503     }
504   }
505   return NULL;
506 }
507 
508 struct TileCacheStartPageInfo {
509   TileCache *tileCache;
510   CachedTileDesc *ct;
511   SplashOutputDev *out;
512 };
513 
startPageCbk(void * data)514 void TileCache::startPageCbk(void *data) {
515   TileCacheStartPageInfo *info = (TileCacheStartPageInfo *)data;
516 
517   info->tileCache->threadPool->lockMutex();
518   info->ct->bitmap = info->out->getBitmap();
519   info->ct->freeBitmap = gFalse;
520   info->tileCache->threadPool->unlockMutex();
521 }
522 
523 // Rasterize <tile>.  The state has already been set to
524 // cachedTileStarted.
rasterizeTile(CachedTileDesc * ct)525 void TileCache::rasterizeTile(CachedTileDesc *ct) {
526   SplashOutputDev *out;
527   TileCacheStartPageInfo info;
528 
529 
530   out = new SplashOutputDev(state->getColorMode(), 1, state->getReverseVideo(),
531 			    state->getPaperColor());
532   info.tileCache = this;
533   info.ct = ct;
534   info.out = out;
535   out->setStartPageCallback(&TileCache::startPageCbk, &info);
536   out->startDoc(state->getDoc()->getXRef());
537   state->getDoc()->displayPageSlice(out, ct->page, ct->dpi, ct->dpi, ct->rotate,
538 				    gFalse, gTrue, gFalse,
539 				    ct->tx, ct->ty, ct->tw, ct->th,
540 				    &abortCheckCbk, ct);
541   if (ct->state == cachedTileCanceled) {
542     threadPool->lockMutex();
543     removeTile(ct);
544     threadPool->unlockMutex();
545   } else {
546     threadPool->lockMutex();
547     ct->bitmap = out->takeBitmap();
548     ct->freeBitmap = gTrue;
549     ct->state = cachedTileFinished;
550     threadPool->unlockMutex();
551     if (tileDoneCbk) {
552       (*tileDoneCbk)(tileDoneCbkData);
553     }
554   }
555   delete out;
556 }
557 
558 
abortCheckCbk(void * data)559 GBool TileCache::abortCheckCbk(void *data) {
560   CachedTileDesc *ct = (CachedTileDesc *)data;
561   return ct->state == cachedTileCanceled;
562 }
563