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