Files
Yajbir Singh f1b860b25c
Some checks failed
check / markdownlint (push) Has been cancelled
check / spellchecker (push) Has been cancelled
updated
2025-12-11 19:03:17 +05:30

563 lines
14 KiB
C++

//========================================================================
//
// 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;
}