Threading rewrite.

develop
Warmist 2013-06-29 20:55:07 +03:00
parent b248eb3e52
commit ee1bad84fc
2 changed files with 292 additions and 198 deletions

@ -35,93 +35,18 @@ using namespace tthread;
const float RootTwo = 1.4142135623730950488016887242097f; const float RootTwo = 1.4142135623730950488016887242097f;
void lightingEngineViewscreen::lightWorkerThread(void * arg) bool isInRect(const coord2d& pos,const rect2d& rect)
{ {
int thisIndex; if(pos.x>=rect.first.x && pos.y>=rect.first.y && pos.x<rect.second.x && pos.y<rect.second.y)
std::vector<lightCell> canvas; return true;
while(1) return false;
{
writeMutex.lock();
writeMutex.unlock(); //Don't start till write access is given.
indexMutex.lock();
if(nextIndex == -1) //The worker threads should keep going until, and including, index 0.
{
indexMutex.unlock();
break;
}
else if(nextIndex == -2)
{
indexMutex.unlock();
return;
}
else
{
thisIndex = nextIndex;
nextIndex--;
indexMutex.unlock();
if(canvas.size() != lightMap.size())
{
canvas.resize(lightMap.size(), lightCell(0,0,0));
}
doLight(canvas, thisIndex);
}
}
writeMutex.lock();
for(int i = 0; i < canvas.size(); i++)
{
lightMap[i] = blend(lightMap[i], canvas[i]);
}
writeMutex.unlock();
canvas.assign(canvas.size(),lightCell(0,0,0));
} }
lightingEngineViewscreen::~lightingEngineViewscreen() lightingEngineViewscreen::~lightingEngineViewscreen()
{ {
indexMutex.lock(); threading.shutdown();
nextIndex = -2;
indexMutex.unlock();
writeMutex.unlock();
for(int i = 0; i < threadList.size(); i++)
{
if(threadList[i])
threadList[i]->join();
}
} }
void threadStub(void * arg)
{
if(arg)
((lightingEngineViewscreen*)arg)->lightWorkerThread(0);
}
void lightingEngineViewscreen::doLightThreads()
{
nextIndex = 0;
int num_threads = thread::hardware_concurrency();
if(num_threads < 1) num_threads = 1;
if(threadList.empty())
{
threadList.resize(num_threads, NULL);
}
for(int i = 0; i < num_threads; i++)
{
threadList[i] = new thread(threadStub, this);
}
nextIndex = lightMap.size() - 1; //start at the largest valid index
writeMutex.unlock();
for(int i = 0; i < num_threads; i++)
{
threadList[i]->join();
delete threadList[i];
threadList[i]=0;
}
writeMutex.lock();
}
lightSource::lightSource(lightCell power,int radius):power(power),flicker(false) lightSource::lightSource(lightCell power,int radius):power(power),flicker(false)
{ {
if(radius >= 0) if(radius >= 0)
@ -176,12 +101,14 @@ rect2d getMapViewport()
} }
return mkrect_wh(1,1,view_rb,view_height+1); return mkrect_wh(1,1,view_rb,view_height+1);
} }
lightingEngineViewscreen::lightingEngineViewscreen(renderer_light* target):lightingEngine(target),doDebug(false) lightingEngineViewscreen::lightingEngineViewscreen(renderer_light* target):lightingEngine(target),doDebug(false),threading(this)
{ {
reinit(); reinit();
defaultSettings(); defaultSettings();
loadSettings(); loadSettings();
writeMutex.lock(); //This is needed for later when the threads will all want to write to the buffer. int numTreads=tthread::thread::hardware_concurrency();
if(numTreads==0)numTreads=1;
threading.start(numTreads);
} }
void lightingEngineViewscreen::reinit() void lightingEngineViewscreen::reinit()
@ -301,100 +228,6 @@ lightCell blend(lightCell a,lightCell b)
{ {
return blendMax(a,b); return blendMax(a,b);
} }
lightCell lightingEngineViewscreen::lightUpCell(std::vector<lightCell> & target,lightCell power,int dx,int dy,int tx,int ty)
{
if(isInViewport(coord2d(tx,ty),mapPort))
{
size_t tile=getIndex(tx,ty);
int dsq=dx*dx+dy*dy;
float dt=1;
if(dsq == 1)
dt=1;
else if(dsq == 2)
dt = RootTwo;
else if(dsq == 0)
dt = 0;
else
dt=sqrt((float)dsq);
lightCell& v=ocupancy[tile];
lightSource& ls=lights[tile];
bool wallhack=false;
if(v.r+v.g+v.b==0)
wallhack=true;
if (dsq>0 && !wallhack)
{
power*=v.pow(dt);
}
if(ls.radius>0 && dsq>0)
{
if(power<=ls.power)
return lightCell();
}
//float dt=sqrt(dsq);
lightCell oldCol=target[tile];
lightCell ncol=blendMax(power,oldCol);
target[tile]=ncol;
if(wallhack)
return lightCell();
return power;
}
else
return lightCell();
}
void lightingEngineViewscreen::doRay(std::vector<lightCell> & target, lightCell power,int cx,int cy,int tx,int ty)
{
using namespace std::placeholders;
plotLine(cx,cy,tx,ty,power,std::bind(&lightingEngineViewscreen::lightUpCell,this,std::ref(target),_1,_2,_3,_4,_5));
}
void lightingEngineViewscreen::doLight(std::vector<lightCell> & target, int index)
{
using namespace std::placeholders;
lightSource& csource=lights[index];
if(csource.radius>0)
{
coord2d coord = getCoords(index);
int i = coord.x;
int j = coord.y;
lightCell power=csource.power;
int radius =csource.radius;
if(csource.flicker)
{
float flicker=(rand()/(float)RAND_MAX)/2.0f+0.5f;
radius*=flicker;
power=power*flicker;
}
lightCell surrounds;
lightUpCell(target,power, 0, 0,i+0, j+0);
{
surrounds += lightUpCell(target, power, 0, 1,i+0, j+1);
surrounds += lightUpCell(target, power, 1, 1,i+1, j+1);
surrounds += lightUpCell(target, power, 1, 0,i+1, j+0);
surrounds += lightUpCell(target, power, 1,-1,i+1, j-1);
surrounds += lightUpCell(target, power, 0,-1,i+0, j-1);
surrounds += lightUpCell(target, power,-1,-1,i-1, j-1);
surrounds += lightUpCell(target, power,-1, 0,i-1, j+0);
surrounds += lightUpCell(target, power,-1, 1,i-1, j+1);
}
if(surrounds.dot(surrounds)>0.00001f)
{
plotSquare(i,j,radius,
std::bind(&lightingEngineViewscreen::doRay,this,std::ref(target),power,i,j,_1,_2));
}
}
}
void lightingEngineViewscreen::doFovs()
{
mapPort=getMapViewport();
doLightThreads();
}
void lightingEngineViewscreen::clear() void lightingEngineViewscreen::clear()
{ {
lightMap.assign(lightMap.size(),lightCell(1,1,1)); lightMap.assign(lightMap.size(),lightCell(1,1,1));
@ -417,7 +250,8 @@ void lightingEngineViewscreen::calculate()
lightMap[getIndex(i,j)]=dim; lightMap[getIndex(i,j)]=dim;
} }
doOcupancyAndLights(); doOcupancyAndLights();
doFovs(); threading.signalDoneOcclusion();
threading.waitForWrites();
} }
void lightingEngineViewscreen::updateWindow() void lightingEngineViewscreen::updateWindow()
{ {
@ -586,12 +420,7 @@ coord2d lightingEngineViewscreen::worldToViewportCoord(const coord2d& in,const r
{ {
return in-window2d+r.first; return in-window2d+r.first;
} }
bool lightingEngineViewscreen::isInViewport(const coord2d& in,const rect2d& r)
{
if(in.x>=r.first.x && in.y>=r.first.y && in.x<r.second.x && in.y<r.second.y)
return true;
return false;
}
static size_t max_list_size = 100000; // Avoid iterating over huge lists static size_t max_list_size = 100000; // Avoid iterating over huge lists
void lightingEngineViewscreen::doSun(const lightSource& sky,MapExtras::MapCache& map) void lightingEngineViewscreen::doSun(const lightSource& sky,MapExtras::MapCache& map)
{ {
@ -642,7 +471,7 @@ void lightingEngineViewscreen::doSun(const lightSource& sky,MapExtras::MapCache&
pos.x = blockX*16+block_x; pos.x = blockX*16+block_x;
pos.y = blockY*16+block_y; pos.y = blockY*16+block_y;
pos=worldToViewportCoord(pos,vp,window2d); pos=worldToViewportCoord(pos,vp,window2d);
if(isInViewport(pos,vp) && curCell.dot(curCell)>0.003f) if(isInRect(pos,vp) && curCell.dot(curCell)>0.003f)
{ {
lightSource sun=lightSource(curCell,15); lightSource sun=lightSource(curCell,15);
addLight(getIndex(pos.x,pos.y),sun); addLight(getIndex(pos.x,pos.y),sun);
@ -719,7 +548,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
pos.y = blockY*16+block_y; pos.y = blockY*16+block_y;
df::coord2d gpos=pos; df::coord2d gpos=pos;
pos=worldToViewportCoord(pos,vp,window2d); pos=worldToViewportCoord(pos,vp,window2d);
if(!isInViewport(pos,vp)) if(!isInRect(pos,vp))
continue; continue;
int tile=getIndex(pos.x,pos.y); int tile=getIndex(pos.x,pos.y);
lightCell& curCell=ocupancy[tile]; lightCell& curCell=ocupancy[tile];
@ -790,7 +619,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
df::coord2d pos=f->pos; df::coord2d pos=f->pos;
pos=worldToViewportCoord(pos,vp,window2d); pos=worldToViewportCoord(pos,vp,window2d);
int tile=getIndex(pos.x,pos.y); int tile=getIndex(pos.x,pos.y);
if(isInViewport(pos,vp)) if(isInRect(pos,vp))
{ {
lightCell fireColor; lightCell fireColor;
if(f->density>60) if(f->density>60)
@ -819,7 +648,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
df::coord2d pos=cPlant->pos; df::coord2d pos=cPlant->pos;
pos=worldToViewportCoord(pos,vp,window2d); pos=worldToViewportCoord(pos,vp,window2d);
int tile=getIndex(pos.x,pos.y); int tile=getIndex(pos.x,pos.y);
if(isInViewport(pos,vp)) if(isInRect(pos,vp))
{ {
applyMaterial(tile,419,cPlant->material); applyMaterial(tile,419,cPlant->material);
} }
@ -847,7 +676,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
if(amount<=0) if(amount<=0)
continue; continue;
pos=worldToViewportCoord(pos,vp,window2d); pos=worldToViewportCoord(pos,vp,window2d);
if(isInViewport(pos,vp)) if(isInRect(pos,vp))
{ {
addLight(getIndex(pos.x,pos.y),m->makeSource((float)amount/100)); addLight(getIndex(pos.x,pos.y),m->makeSource((float)amount/100));
} }
@ -868,7 +697,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
{ {
df::unit *u = df::global::world->units.active[i]; df::unit *u = df::global::world->units.active[i];
coord2d pos=worldToViewportCoord(coord2d(u->pos.x,u->pos.y),vp,window2d); coord2d pos=worldToViewportCoord(coord2d(u->pos.x,u->pos.y),vp,window2d);
if(u->pos.z==window_z && isInViewport(pos,vp)) if(u->pos.z==window_z && isInRect(pos,vp))
if (DFHack::Units::isCitizen(u) && !u->counters.unconscious) if (DFHack::Units::isCitizen(u) && !u->counters.unconscious)
addLight(getIndex(pos.x,pos.y),matCitizen.makeSource()); addLight(getIndex(pos.x,pos.y),matCitizen.makeSource());
} }
@ -886,11 +715,11 @@ void lightingEngineViewscreen::doOcupancyAndLights()
df::coord2d p2(bld->x2,bld->y2); df::coord2d p2(bld->x2,bld->y2);
p1=worldToViewportCoord(p1,vp,window2d); p1=worldToViewportCoord(p1,vp,window2d);
p2=worldToViewportCoord(p2,vp,window2d); p2=worldToViewportCoord(p2,vp,window2d);
if(isInViewport(p1,vp)||isInViewport(p2,vp)) if(isInRect(p1,vp)||isInRect(p2,vp))
{ {
int tile; int tile;
if(isInViewport(p1,vp)) if(isInRect(p1,vp))
tile=getIndex(p1.x,p1.y); //TODO multitile buildings. How they would work? tile=getIndex(p1.x,p1.y); //TODO multitile buildings. How they would work?
else else
tile=getIndex(p2.x,p2.y); tile=getIndex(p2.x,p2.y);
@ -1197,3 +1026,216 @@ void lightingEngineViewscreen::loadSettings()
} }
#undef GETLUAFLAG #undef GETLUAFLAG
#undef GETLUANUMBER #undef GETLUANUMBER
/*
* Threading stuff
*/
lightThread::lightThread( lightThreadDispatch& dispatch ):dispatch(dispatch),isDone(false),myThread(0)
{
}
lightThread::~lightThread()
{
if(myThread)
delete myThread;
}
void lightThread::run()
{
while(!isDone)
{
{
tthread::lock_guard<tthread::mutex> guard(dispatch.occlusionMutex);
dispatch.occlusionDone.wait(dispatch.occlusionMutex);//wait for work
if(dispatch.unprocessed.size()==0) //spurious wake-up
continue;
if(dispatch.occlusion.size()!=canvas.size()) //oh no somebody resized stuff
canvas.resize(dispatch.occlusion.size());
}
{ //get my rectangle (any will do)
tthread::lock_guard<tthread::mutex> guard(dispatch.unprocessedMutex);
myRect=dispatch.unprocessed.top();
dispatch.unprocessed.pop();
}
work();
{
tthread::lock_guard<tthread::mutex> guard(dispatch.writeLock);
combine();//write it back
dispatch.writeCount++;
}
dispatch.writesDone.notify_one();//tell about it to the dispatch.
}
}
void lightThread::work()
{
canvas.assign(canvas.size(),lightCell(0,0,0));
for(int i=myRect.first.x;i<myRect.second.x;i++)
for(int j=myRect.first.y;j<myRect.second.y;j++)
{
doLight(i,j);
}
}
void lightThread::combine()
{
for(int i=0;i<canvas.size();i++)
{
lightCell& c=dispatch.lightMap[i];
c=blend(c,canvas[i]);
}
}
lightCell lightThread::lightUpCell(lightCell power,int dx,int dy,int tx,int ty)
{
int h=dispatch.getH();
if(isInRect(coord2d(tx,ty),dispatch.viewPort))
{
size_t tile=tx*h+ty;
int dsq=dx*dx+dy*dy;
float dt=1;
if(dsq == 1)
dt=1;
else if(dsq == 2)
dt = RootTwo;
else if(dsq == 0)
dt = 0;
else
dt=sqrt((float)dsq);
lightCell& v=dispatch.occlusion[tile];
lightSource& ls=dispatch.lights[tile];
bool wallhack=false;
if(v.r+v.g+v.b==0)
wallhack=true;
if (dsq>0 && !wallhack)
{
power*=v.pow(dt);
}
if(ls.radius>0 && dsq>0)
{
if(power<=ls.power)
return lightCell();
}
//float dt=sqrt(dsq);
lightCell oldCol=canvas[tile];
lightCell ncol=blendMax(power,oldCol);
canvas[tile]=ncol;
if(wallhack)
return lightCell();
return power;
}
else
return lightCell();
}
void lightThread::doRay(lightCell power,int cx,int cy,int tx,int ty)
{
using namespace std::placeholders;
plotLine(cx,cy,tx,ty,power,std::bind(&lightThread::lightUpCell,this,_1,_2,_3,_4,_5));
}
void lightThread::doLight( int x,int y )
{
using namespace std::placeholders;
lightSource& csource=dispatch.lights[x*dispatch.getH()+y];
if(csource.radius>0)
{
lightCell power=csource.power;
int radius =csource.radius;
if(csource.flicker)
{
float flicker=(rand()/(float)RAND_MAX)/2.0f+0.5f;
radius*=flicker;
power=power*flicker;
}
lightCell surrounds;
lightUpCell( power, 0, 0,x, y);
for(int i=-1;i<2;i++)
for(int j=-1;j<2;j++)
if(i!=0||j!=0)
surrounds += lightUpCell( power, i, j,x+i, y+j);
if(surrounds.dot(surrounds)>0.00001f)
{
plotSquare(x,y,radius,
std::bind(&lightThread::doRay,this,power,x,y,_1,_2));
}
}
}
void lightThreadDispatch::signalDoneOcclusion()
{
{
tthread::lock_guard<tthread::mutex> guardWrite(writeLock);
writeCount=0;
}
tthread::lock_guard<tthread::mutex> guard(occlusionMutex);
viewPort=getMapViewport();
int threadCount=threadPool.size();
int w=viewPort.second.x-viewPort.first.x;
int slicew=w/threadCount;
for(int i=0;i<threadCount;i++)
{
rect2d area=viewPort;
area.first.x=viewPort.first.x+i*slicew;
if(i==threadCount-1)
area.second.x=viewPort.second.x; //fix, when area is even number or sth.
else
area.second.x=viewPort.first.x+(i+1)*slicew;
unprocessed.push(area);
}
occlusionDone.notify_all();
}
lightThreadDispatch::lightThreadDispatch( lightingEngineViewscreen* p ):parent(p),lights(parent->lights),occlusion(parent->ocupancy),lightMap(parent->lightMap),writeCount(0)
{
}
void lightThreadDispatch::shutdown()
{
for(int i=0;i<threadPool.size();i++)
{
threadPool[i]->isDone=true;
threadPool[i]->myThread->join();
}
threadPool.clear();
}
int lightThreadDispatch::getW()
{
return parent->getW();
}
int lightThreadDispatch::getH()
{
return parent->getH();
}
void threadStub(void * arg)
{
if(arg)
((lightThread*)arg)->run();
}
void lightThreadDispatch::start(int count)
{
for(int i=0;i<count;i++)
{
std::unique_ptr<lightThread> nthread(new lightThread(*this));
nthread->myThread=new tthread::thread(&threadStub,nthread.get());
threadPool.push_back(std::move(nthread));
}
}
void lightThreadDispatch::waitForWrites()
{
tthread::lock_guard<tthread::mutex> guard(writeLock);
while(threadPool.size()>writeCount)//missed it somehow already.
{
writesDone.wait(writeLock); //if not, wait a bit
}
}

@ -4,8 +4,10 @@
#include "Types.h" #include "Types.h"
#include <map> #include <map>
#include <tuple> #include <tuple>
#include <stack>
#include <memory>
#include "modules/MapCache.h" #include "modules/MapCache.h"
bool isInRect(const df::coord2d& pos,const DFHack::rect2d& rect);
struct renderer_light : public renderer_wrap { struct renderer_light : public renderer_wrap {
private: private:
void colorizeTile(int x,int y) void colorizeTile(int x,int y)
@ -130,6 +132,56 @@ struct buildingLightDef
float size; float size;
buildingLightDef():poweredOnly(false),useMaterial(true),thickness(1.0f),size(1.0f){} buildingLightDef():poweredOnly(false),useMaterial(true),thickness(1.0f),size(1.0f){}
}; };
class lightThread;
class lightingEngineViewscreen;
class lightThreadDispatch
{
lightingEngineViewscreen *parent;
public:
DFHack::rect2d viewPort;
std::vector<std::unique_ptr<lightThread> > threadPool;
std::vector<lightSource>& lights;
tthread::mutex occlusionMutex;
tthread::condition_variable occlusionDone; //all threads wait for occlusion to finish
tthread::mutex unprocessedMutex;
std::stack<DFHack::rect2d> unprocessed; //stack of parts of map where lighting is not finished
std::vector<lightCell>& occlusion;
tthread::mutex writeLock; //mutex for lightMap
std::vector<lightCell>& lightMap;
tthread::condition_variable writesDone;
int writeCount;
lightThreadDispatch(lightingEngineViewscreen* p);
void signalDoneOcclusion();
void shutdown();
void waitForWrites();
int getW();
int getH();
void start(int count);
};
class lightThread
{
std::vector<lightCell> canvas;
lightThreadDispatch& dispatch;
DFHack::rect2d myRect;
void work(); //main light calculation function
void combine(); //combine existing canvas into global lightmap
public:
tthread::thread *myThread;
bool isDone; //no mutex, because bool is atomic
lightThread(lightThreadDispatch& dispatch);
~lightThread();
void run();
private:
void doLight(int x,int y);
void doRay(lightCell power,int cx,int cy,int tx,int ty);
lightCell lightUpCell(lightCell power,int dx,int dy,int tx,int ty);
};
class lightingEngineViewscreen:public lightingEngine class lightingEngineViewscreen:public lightingEngine
{ {
public: public:
@ -147,7 +199,7 @@ public:
private: private:
df::coord2d worldToViewportCoord(const df::coord2d& in,const DFHack::rect2d& r,const df::coord2d& window2d) ; df::coord2d worldToViewportCoord(const df::coord2d& in,const DFHack::rect2d& r,const df::coord2d& window2d) ;
bool isInViewport(const df::coord2d& in,const DFHack::rect2d& r);
void doSun(const lightSource& sky,MapExtras::MapCache& map); void doSun(const lightSource& sky,MapExtras::MapCache& map);
void doOcupancyAndLights(); void doOcupancyAndLights();
@ -181,13 +233,12 @@ private:
std::vector<lightSource> lights; std::vector<lightSource> lights;
//Threading stuff //Threading stuff
tthread::mutex indexMutex; lightThreadDispatch threading;
tthread::mutex writeMutex;
int nextIndex;
std::vector<tthread::thread *> threadList;
void doLightThreads();
//misc //misc
void setHour(float h){dayHour=h;}; void setHour(float h){dayHour=h;};
int getW()const {return w;}
int getH()const {return h;}
public: public:
void lightWorkerThread(void * arg); void lightWorkerThread(void * arg);
private: private:
@ -219,6 +270,7 @@ private:
std::map<std::tuple<int,int,int>,buildingLightDef> buildingDefs; std::map<std::tuple<int,int,int>,buildingLightDef> buildingDefs;
int w,h; int w,h;
DFHack::rect2d mapPort; DFHack::rect2d mapPort;
friend lightThreadDispatch;
}; };
lightCell blend(lightCell a,lightCell b); lightCell blend(lightCell a,lightCell b);
lightCell blendMax(lightCell a,lightCell b); lightCell blendMax(lightCell a,lightCell b);