475 lines
11 KiB
C++
Executable File
475 lines
11 KiB
C++
Executable File
//***************************************************************************
|
|
// ScanLinePageMgr.h
|
|
// A scanline based memory-to-disk pager template
|
|
// Christer Janson
|
|
// Discreet, A division of Autodesk, Inc.
|
|
// San Francisco, CA - November 15, 1999
|
|
|
|
#ifndef _SCANLINEPAGEMGR__H_
|
|
#define _SCANLINEPAGEMGR__H_
|
|
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <sys\stat.h>
|
|
|
|
#define SCANLINE_PAGE_INMEMORY 0x01
|
|
#define SCANLINE_PAGE_NEEDWRITE 0x02
|
|
#define SCANLINE_PAGE_HASDISKSTORAGE 0x04
|
|
#define SCANLINE_PAGE_LOCKED 0x08
|
|
#define SCANLINE_PAGE_VALIDDISK 0x10
|
|
|
|
template <class T> class ScanLinePage {
|
|
public:
|
|
ScanLinePage(int width, int numScanLines, int firstScanLine, int pageSize, int fileIndex)
|
|
{
|
|
mFlags = 0;
|
|
mLockCount = 0;
|
|
mWidth = width;
|
|
mNumScanLines = numScanLines;
|
|
mFirstScanLine = firstScanLine;
|
|
mFileIndex = fileIndex;
|
|
mPageSize = pageSize;
|
|
|
|
mData = NULL;
|
|
ClearInMemory();
|
|
ClearValidDisk();
|
|
SetHasDiskStorage();
|
|
ClearNeedWrite();
|
|
}
|
|
|
|
ScanLinePage(int width, int height, bool* allocOk)
|
|
{
|
|
mFlags = 0;
|
|
mLockCount = 0;
|
|
mWidth = width;
|
|
mNumScanLines = height;
|
|
mFirstScanLine = 0;
|
|
mFileIndex = 0;
|
|
mPageSize = 0;
|
|
|
|
mData = (T*)malloc(width*height*sizeof(T));
|
|
if (mData) {
|
|
*allocOk = true;
|
|
memset(mData, 0, width*height*sizeof(T));
|
|
}
|
|
else {
|
|
*allocOk = false;
|
|
}
|
|
SetInMemory();
|
|
ClearHasDiskStorage();
|
|
ClearNeedWrite();
|
|
ClearValidDisk();
|
|
}
|
|
|
|
|
|
~ScanLinePage()
|
|
{
|
|
if (mData) {
|
|
free(mData);
|
|
}
|
|
}
|
|
|
|
void PreparePage(int fileHandle)
|
|
{
|
|
// If it's allocated, it's already prepared
|
|
if (!mData) {
|
|
mData = (T*)malloc(mPageSize);
|
|
SetInMemory();
|
|
if (GetValidDisk()) {
|
|
ReadPage(fileHandle);
|
|
}
|
|
else {
|
|
memset(mData, 0, mPageSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlushPage(int fileHandle)
|
|
{
|
|
if (GetHasDiskStorage() && !IsPageLocked()) {
|
|
if (GetInMemory()) {
|
|
if (GetNeedWrite()) {
|
|
WritePage(fileHandle);
|
|
}
|
|
free(mData);
|
|
mData = NULL;
|
|
ClearInMemory();
|
|
}
|
|
}
|
|
}
|
|
|
|
T* GetBuffer(int scanline, bool needWrite)
|
|
{
|
|
if (needWrite) {
|
|
SetNeedWrite();
|
|
}
|
|
return &mData[(scanline-mFirstScanLine)*mWidth];
|
|
}
|
|
|
|
T* GetBuffer()
|
|
{
|
|
if (GetInMemory())
|
|
return mData;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
void LockPage() { mLockCount++; }
|
|
void UnlockPage() { mLockCount--; }
|
|
bool IsPageLocked() { return (mLockCount > 0) ? true : false; }
|
|
|
|
private:
|
|
void SetInMemory() { mFlags |= SCANLINE_PAGE_INMEMORY; }
|
|
void ClearInMemory() { mFlags &= ~SCANLINE_PAGE_INMEMORY; }
|
|
bool GetInMemory() { return (mFlags & SCANLINE_PAGE_INMEMORY) ? true : false; }
|
|
|
|
void SetNeedWrite() { mFlags |= SCANLINE_PAGE_NEEDWRITE; }
|
|
void ClearNeedWrite(){ mFlags &= ~SCANLINE_PAGE_NEEDWRITE; }
|
|
bool GetNeedWrite() { return (mFlags & SCANLINE_PAGE_NEEDWRITE) ? true : false; }
|
|
|
|
void SetHasDiskStorage() { mFlags |= SCANLINE_PAGE_HASDISKSTORAGE; }
|
|
void ClearHasDiskStorage() { mFlags &= ~SCANLINE_PAGE_HASDISKSTORAGE; }
|
|
bool GetHasDiskStorage() { return (mFlags & SCANLINE_PAGE_HASDISKSTORAGE) ? true : false; }
|
|
|
|
void SetValidDisk() { mFlags |= SCANLINE_PAGE_VALIDDISK; }
|
|
void ClearValidDisk(){ mFlags &= ~SCANLINE_PAGE_VALIDDISK; }
|
|
bool GetValidDisk() { return (mFlags & SCANLINE_PAGE_VALIDDISK) ? true : false; }
|
|
|
|
int GetFileIndex() { return mFileIndex; }
|
|
int GetPageSize() { return mPageSize; }
|
|
|
|
void ReadPage(int fileHandle)
|
|
{
|
|
int fileIndex = GetFileIndex();
|
|
int pageSize = GetPageSize();
|
|
_lseek(fileHandle, fileIndex, SEEK_SET);
|
|
_read(fileHandle, mData, pageSize);
|
|
ClearNeedWrite();
|
|
SetInMemory();
|
|
}
|
|
|
|
void WritePage(int fileHandle)
|
|
{
|
|
int fileIndex = GetFileIndex();
|
|
int pageSize = GetPageSize();
|
|
_lseek(fileHandle, fileIndex, SEEK_SET);
|
|
_write(fileHandle, mData, pageSize);
|
|
ClearNeedWrite();
|
|
SetValidDisk();
|
|
}
|
|
|
|
private:
|
|
T* mData;
|
|
int mFileIndex;
|
|
int mFirstScanLine;
|
|
int mNumScanLines;
|
|
int mWidth;
|
|
int mPageSize;
|
|
int mLockCount;
|
|
BYTE mFlags;
|
|
};
|
|
|
|
template <class T> class ScanLinePageMgr {
|
|
public:
|
|
ScanLinePageMgr()
|
|
{
|
|
mFileHandle = -1;
|
|
mScanLinesPerPage = 0;
|
|
mNumPages = 0;
|
|
mWidth = 0;
|
|
mFilename = NULL;
|
|
mIsPaged = 0;
|
|
|
|
SYSTEM_INFO si;
|
|
GetSystemInfo(&si);
|
|
mNumPagesInMemory = 2*si.dwNumberOfProcessors;
|
|
pMru = new int[mNumPagesInMemory];
|
|
|
|
InitializeCriticalSection(&cs);
|
|
for (int p=0; p<mNumPagesInMemory; p++) {
|
|
pMru[p] = -1;
|
|
}
|
|
}
|
|
|
|
bool Initialize(int width, int height, int memThreshold, int pageSize)
|
|
{
|
|
mWidth = width;
|
|
int totalSize = width * height * sizeof(T);
|
|
if (memThreshold && (totalSize > memThreshold)) {
|
|
mIsPaged = true;
|
|
|
|
char baseName[_MAX_PATH];
|
|
char fn[_MAX_PATH];
|
|
|
|
// TBD: Need a system page directory
|
|
strcpy(baseName, GetCOREInterface()->GetDir(APP_AUTOBACK_DIR));
|
|
if (strlen(baseName) && baseName[strlen(baseName)-1] != '\\') {
|
|
strcat(baseName, "\\");
|
|
}
|
|
|
|
// Generate a unique filename
|
|
bool bGotUniqueName = false;
|
|
int tmpIndex = 0;
|
|
while (!bGotUniqueName) {
|
|
sprintf(fn, "%s3dsmax_pager%d.tmp", baseName, tmpIndex);
|
|
if (_access(fn, 0) !=0) {
|
|
bGotUniqueName = true;
|
|
}
|
|
tmpIndex++;
|
|
}
|
|
|
|
mFilename = (char*)malloc(strlen(fn)+1);
|
|
if (!mFilename) {
|
|
return false;
|
|
}
|
|
|
|
strcpy(mFilename, fn);
|
|
|
|
// These temporary files are deleted by the system when the last handle is closed.
|
|
mFileHandle = _open(mFilename, _O_CREAT | O_TRUNC | _O_TEMPORARY | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
|
|
if (mFileHandle == -1) {
|
|
return false;
|
|
}
|
|
|
|
if (_chsize(mFileHandle, totalSize) == -1) {
|
|
return false;
|
|
}
|
|
|
|
int scanLineSize = width*sizeof(T);
|
|
if (pageSize < scanLineSize) {
|
|
pageSize = scanLineSize;
|
|
}
|
|
|
|
// By rounding up the pageSize we get it aligned to the actual scanLine boundaries.
|
|
mScanLinesPerPage = pageSize/scanLineSize;
|
|
pageSize = mScanLinesPerPage*scanLineSize;
|
|
|
|
mNumPages = height/mScanLinesPerPage;
|
|
if (mNumPages*mScanLinesPerPage < height)
|
|
mNumPages++;
|
|
|
|
for (int i=0; i<mNumPages; i++) {
|
|
int fileIndex = i*pageSize;
|
|
ScanLinePage<T>* page = new ScanLinePage<T>(width, mScanLinesPerPage, mScanLinesPerPage*i, pageSize, fileIndex);
|
|
if (!page) {
|
|
_close(mFileHandle);
|
|
return false;
|
|
}
|
|
mPageTable.Append(1, &page, mNumPages);
|
|
}
|
|
}
|
|
else {
|
|
mIsPaged = false;
|
|
mFilename = NULL;
|
|
|
|
bool allocOk = false;
|
|
ScanLinePage<T>* page = new ScanLinePage<T>(width, height, &allocOk);
|
|
if (!page) {
|
|
return false;
|
|
}
|
|
if (!allocOk) {
|
|
delete page;
|
|
return false;
|
|
}
|
|
mPageTable.Append(1, &page, 0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
~ScanLinePageMgr()
|
|
{
|
|
for (int i=0; i<mPageTable.Count(); i++) {
|
|
ScanLinePage<T>* page = mPageTable[i];
|
|
delete page;
|
|
}
|
|
mPageTable.ZeroCount();
|
|
mPageTable.Shrink();
|
|
|
|
if (mIsPaged) {
|
|
_close(mFileHandle);
|
|
free(mFilename);
|
|
}
|
|
|
|
delete [] pMru;
|
|
|
|
DeleteCriticalSection(&cs);
|
|
}
|
|
|
|
// Returns a pointer to the storage is the map is not paged.
|
|
T* GetBuffer()
|
|
{
|
|
if (!mIsPaged)
|
|
{
|
|
return mPageTable[0]->GetBuffer();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const T* OpenScanLine(int scanline)
|
|
{
|
|
if (!mIsPaged) {
|
|
return mPageTable[0]->GetBuffer(scanline, false);
|
|
}
|
|
else {
|
|
int activePage = GetPageIndex(scanline);
|
|
EnterCriticalSection(&cs);
|
|
|
|
AddPageToMRU(activePage);
|
|
|
|
FlushUnusedPages();
|
|
ScanLinePage<T>* page = GetPage(activePage);
|
|
page->LockPage();
|
|
page->PreparePage(GetFileHandle());
|
|
LeaveCriticalSection(&cs);
|
|
|
|
return page->GetBuffer(scanline, false);
|
|
}
|
|
}
|
|
|
|
void CloseScanLine(int scanline)
|
|
{
|
|
if (mIsPaged) {
|
|
int activePage = GetPageIndex(scanline);
|
|
EnterCriticalSection(&cs);
|
|
ScanLinePage<T>* page = GetPage(activePage);
|
|
page->UnlockPage();
|
|
LeaveCriticalSection(&cs);
|
|
}
|
|
}
|
|
|
|
bool PutScanLine(int scanline, const T* buf)
|
|
{
|
|
if (!mIsPaged) {
|
|
memcpy(mPageTable[0]->GetBuffer(scanline, true), buf, mWidth*sizeof(T));
|
|
}
|
|
else {
|
|
int activePage = GetPageIndex(scanline);
|
|
EnterCriticalSection(&cs);
|
|
|
|
AddPageToMRU(activePage);
|
|
|
|
FlushUnusedPages();
|
|
ScanLinePage<T>* page = GetPage(activePage);
|
|
page->LockPage();
|
|
page->PreparePage(GetFileHandle());
|
|
memcpy(page->GetBuffer(scanline, true), buf, mWidth*sizeof(T));
|
|
page->UnlockPage();
|
|
LeaveCriticalSection(&cs);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
T GetPixel(int x, int y)
|
|
{
|
|
T col = (T)0;
|
|
const T* buffer = OpenScanLine(y);
|
|
|
|
if (buffer) {
|
|
col = buffer[x];
|
|
}
|
|
|
|
CloseScanLine(y);
|
|
|
|
return col;
|
|
}
|
|
|
|
void PutPixel(int x, int y, T color)
|
|
{
|
|
if (!mIsPaged) {
|
|
T* buffer = mPageTable[0]->GetBuffer(y, true);
|
|
buffer[x] = color;
|
|
}
|
|
else {
|
|
int activePage = GetPageIndex(y);
|
|
EnterCriticalSection(&cs);
|
|
|
|
AddPageToMRU(activePage);
|
|
|
|
FlushUnusedPages();
|
|
ScanLinePage<T>* page = GetPage(activePage);
|
|
page->LockPage();
|
|
page->PreparePage(GetFileHandle());
|
|
|
|
T* buffer = page->GetBuffer(y, true);
|
|
buffer[x] = color;
|
|
|
|
page->UnlockPage();
|
|
LeaveCriticalSection(&cs);
|
|
}
|
|
}
|
|
|
|
private:
|
|
int GetPageIndex(int scanline)
|
|
{
|
|
int pageId = mIsPaged ? (int)((float)scanline / (float)mScanLinesPerPage) : 0;
|
|
return pageId;
|
|
}
|
|
|
|
void AddPageToMRU(int page)
|
|
{
|
|
// speed up multiple access as it's most likely to be on top
|
|
if (pMru[0] == page)
|
|
return;
|
|
|
|
int nListPos = -1;
|
|
// See if page is in the MRU
|
|
for (int p=0; p<mNumPagesInMemory; p++) {
|
|
if (page == pMru[p]) {
|
|
nListPos = p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nListPos != -1) {
|
|
// Page is in the MRU list, move it to the top
|
|
for (int p=nListPos; p>0; p--) {
|
|
pMru[p] = pMru[p-1];
|
|
}
|
|
pMru[0] = page;
|
|
}
|
|
else {
|
|
// Page is not in the MRU list, put it at the top of the list
|
|
for (int p=(mNumPagesInMemory-1); p>0; p--) {
|
|
pMru[p] = pMru[p-1];
|
|
}
|
|
pMru[0] = page;
|
|
}
|
|
}
|
|
|
|
ScanLinePage<T>* GetPage(int pageIndex) { return mPageTable[pageIndex]; }
|
|
void FlushUnusedPages()
|
|
{
|
|
int numPages = GetNumPages();
|
|
|
|
for (int i=0; i<numPages; i++) {
|
|
bool bInMru = false;
|
|
for (int p=0; p<mNumPagesInMemory; p++) {
|
|
if (i == pMru[p]) {
|
|
bInMru = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bInMru) {
|
|
mPageTable[i]->FlushPage(GetFileHandle());
|
|
}
|
|
}
|
|
}
|
|
|
|
int GetFileHandle() { return mFileHandle; }
|
|
int GetNumPages() { return mNumPages; }
|
|
|
|
private:
|
|
Tab <ScanLinePage<T>*> mPageTable;
|
|
int mFileHandle;
|
|
int mScanLinesPerPage;
|
|
int mNumPages;
|
|
int mWidth;
|
|
char* mFilename;
|
|
bool mIsPaged;
|
|
int mNumPagesInMemory;
|
|
int* pMru;
|
|
CRITICAL_SECTION cs;
|
|
};
|
|
|
|
#endif |