#include "trenderer.h"
#include "trendererP.h"
// TnzCore includes
#include "tsystem.h"
#include "tthreadmessage.h"
#include "tthread.h"
#include "tconvert.h"
#include "tstream.h"
#include "trasterimage.h"
#include "trop.h"
#include "timagecache.h"
#include "tstopwatch.h"
// TnzBase includes
#include "trenderresourcemanager.h"
#include "tpredictivecachemanager.h"
// Qt includes
#include <QEventLoop>
#include <QCoreApplication>
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>
#include <QThreadStorage>
// tcg includes
#include "tcg/tcg_deleter_types.h"
//Debug
//#define DIAGNOSTICS
//#include "diagnostics.h"
#include <queue>
#include <functional>
using namespace TThread;
std::vector<const TFx *> calculateSortedFxs(TRasterFxP rootFx);
//================================================================================
// Preliminaries - Anonymous namespace
//================================================================================
namespace
{
//TRenderer-thread association storage. It provides TRenderers the per-thread
//singleton status from inside a rendering process.
QThreadStorage<TRendererImp **> rendererStorage;
//Same for render process ids.
QThreadStorage<unsigned long *> renderIdsStorage;
//-------------------------------------------------------------------------------
//Interlacing functions for field-based rendering
inline void interlace(TRasterP f0, const TRasterP &f1, int field)
{
if (f0->getPixelSize() != f1->getPixelSize())
throw TException("interlace: invalid raster combination");
assert(f0->getBounds() == f1->getBounds());
int outWrapBytes = f0->getWrap() * f0->getPixelSize();
int inWrapBytes = f1->getWrap() * f1->getPixelSize();
int outWrapBytes2 = outWrapBytes * 2;
int inWrapBytes2 = inWrapBytes * 2;
int rowSize = f0->getLx() * f0->getPixelSize();
f1->lock();
f0->lock();
UCHAR *rowIn = f1->getRawData();
UCHAR *rowOut = f0->getRawData();
if (field)
rowIn += inWrapBytes;
int count = f0->getLy() / 2;
while (--count) {
memcpy(rowOut, rowIn, rowSize);
rowIn += inWrapBytes2;
rowOut += outWrapBytes2;
}
f1->unlock();
f0->unlock();
}
} // anonymous namespace
//================================================================================
// Preliminaries - output rasters management
//================================================================================
//===================
// RasterItem
//-------------------
//! The RasterItem class represents a reusable output raster for rendering purposes.
//! RasterItems are used as TRenderer's output rasters in order to avoid the overhead
//! of allocating one raster for each rendered frame.
//! Each frame-rendering task will lock a RasterItem as preallocated output before starting the
//! render, therefore changing the \b RasterItem::m_busy attribute accordingly.
//! As each frame is rendered on a separate thread, the number of RasterItems that
//! TRenderer will allocate depends on the number of rendering threads specified to it.
//! \sa RasterPool, TRenderer classes
class RasterItem
{
string m_rasterId;
public:
int m_bpp;
bool m_busy;
//---------------------------------------------------------
RasterItem(const TDimension &size, int bpp, bool busyFlag)
: m_rasterId(""), m_bpp(bpp), m_busy(busyFlag)
{
TRasterP raster;
if (bpp == 32)
raster = TRaster32P(size);
else if (bpp == 64)
raster = TRaster64P(size);
else
assert(false);
m_rasterId = TImageCache::instance()->getUniqueId();
TImageCache::instance()->add(m_rasterId, TRasterImageP(raster));
}
//---------------------------------------------------------
~RasterItem()
{
TImageCache::instance()->remove(m_rasterId);
}
//---------------------------------------------------------
TRasterP getRaster() const
{
TRasterImageP rimg = (TRasterImageP)TImageCache::instance()->get(m_rasterId, true);
return rimg ? rimg->getRaster() : TRasterP();
}
private:
//not implemented
RasterItem();
RasterItem(const TRaster &RasterItem);
};
//================================================================================
//===================
// RasterPool
//-------------------
//! Stores a list of RasterItems under TRenderer's requests.
class RasterPool
{
TDimension m_size;
int m_bpp;
typedef std::list<RasterItem *> RasterRepository;
RasterRepository m_rasterRepository;
TThread::Mutex m_repositoryLock;
public:
RasterPool() : m_size(-1, -1) {}
~RasterPool();
void setRasterSpecs(const TDimension &size, int bpp);
void getRasterSpecs(TDimension &size, int &bpp)
{
size = m_size;
bpp = m_bpp;
}
TRasterP getRaster();
TRasterP getRaster(const TDimension &size, int bpp);
void releaseRaster(const TRasterP &r);
void clear();
};
//---------------------------------------------------------
void RasterPool::clear()
{
QMutexLocker sl(&m_repositoryLock);
clearPointerContainer(m_rasterRepository);
}
//---------------------------------------------------------
void RasterPool::setRasterSpecs(const TDimension &size, int bpp)
{
if (size != m_size || bpp != m_bpp) {
m_size = size;
m_bpp = bpp;
clear();
}
}
//---------------------------------------------------------
TRasterP RasterPool::getRaster(const TDimension &size, int bpp)
{
assert(size == m_size && bpp == m_bpp);
return getRaster();
}
//---------------------------------------------------------
//!Returns for the first not-busy raster
TRasterP RasterPool::getRaster()
{
QMutexLocker sl(&m_repositoryLock);
RasterRepository::iterator it = m_rasterRepository.begin();
while (it != m_rasterRepository.end()) {
RasterItem *rasItem = *it;
if (rasItem->m_busy == false) {
TRasterP raster = rasItem->getRaster();
if (!raster) {
delete rasItem;
m_rasterRepository.erase(it++);
continue;
}
rasItem->m_busy = true;
raster->clear();
return raster;
}
++it;
}
RasterItem *rasItem = new RasterItem(m_size, m_bpp, true);
m_rasterRepository.push_back(rasItem);
return rasItem->getRaster();
}
//---------------------------------------------------------
//!Cerca il raster \b r in m_rasterRepository; se lo trova setta a \b false il campo \b m_busy.
void RasterPool::releaseRaster(const TRasterP &r)
{
if (!r)
return;
QMutexLocker sl(&m_repositoryLock);
for (RasterRepository::iterator it = m_rasterRepository.begin();
it != m_rasterRepository.end();
++it) {
RasterItem *rasItem = *it;
if (rasItem->getRaster()->getRawData() == r->getRawData()) {
assert(rasItem->m_busy);
rasItem->m_busy = false;
return;
}
}
}
//---------------------------------------------------------
RasterPool::~RasterPool()
{
/*if (m_rasterRepository.size())
TSystem::outputDebug("~RasterPool: itemCount = " + toString ((int)m_rasterRepository.size())+" (should be 0)\n");*/
//Release all raster items
clear();
}
//================================================================================
// Internal rendering classes declaration
//================================================================================
//=====================
// TRendererImp
//---------------------
class TRendererImp : public TSmartObject
{
public:
struct RenderInstanceInfos {
int m_canceled;
int m_activeTasks;
int m_status;
RenderInstanceInfos()
: m_canceled(false), m_activeTasks(0), m_status(TRenderer::IDLE) {}
};
public:
typedef std::vector<TRenderPort *> PortContainer;
typedef PortContainer::iterator PortContainerIterator;
QReadWriteLock m_portsLock;
PortContainer m_ports;
QMutex m_renderInstancesMutex;
std::map<unsigned long, RenderInstanceInfos> m_activeInstances;
//! The renderer Id is a number uniquely associated with a TRenderer instance.
static unsigned long m_rendererIdCounter;
//! The render Id is a number uniquely associated with a render process started
//! with the startRendering() method.
static unsigned long m_renderIdCounter;
unsigned long m_rendererId;
Executor m_executor;
bool m_precomputingEnabled;
RasterPool m_rasterPool;
std::vector<TRenderResourceManager *> m_managers;
TAtomicVar m_undoneTasks;
//std::vector<QEventLoop*> m_waitingLoops;
std::vector<bool *> m_waitingLoops;
TRasterFxP rootFx;
//-------------------------------------------------------------
TRendererImp(int nThreads);
~TRendererImp();
void startRendering(unsigned long renderId, const std::vector<TRenderer::RenderData> &renderDatas);
void notifyRasterStarted(const TRenderPort::RenderData &rd);
void notifyRasterCompleted(const TRenderPort::RenderData &rd);
void notifyRasterFailure(const TRenderPort::RenderData &rd, TException &e);
void notifyRenderFinished(bool isCanceled = false);
void addPort(TRenderPort *port);
void removePort(TRenderPort *port);
void abortRendering(unsigned long renderId);
void stopRendering(bool waitForCompleteStop);
bool hasToDie(unsigned long renderId);
int getRenderStatus(unsigned long renderId);
void enablePrecomputing(bool on) { m_precomputingEnabled = on; }
bool isPrecomputingEnabled() const { return m_precomputingEnabled; }
void setThreadsCount(int nThreads) { m_executor.setMaxActiveTasks(nThreads); }
inline void declareRenderStart(unsigned long renderId);
inline void declareRenderEnd(unsigned long renderId);
inline void declareFrameStart(double frame);
inline void declareFrameEnd(double frame);
inline void declareStatusStart(int renderStatus);
inline void declareStatusEnd(int renderStatus);
void quitWaitingLoops();
};
#ifdef WIN32
template class DVAPI TSmartPointerT<TRendererImp>;
#endif
typedef TSmartPointerT<TRendererImp> TRendererImpP;
unsigned long TRendererImp::m_rendererIdCounter = 0;
unsigned long TRendererImp::m_renderIdCounter = 0;
//================================================================================
//===================
// RenderTask
//-------------------
class RenderTask : public TThread::Runnable
{
std::vector<double> m_frames;
unsigned long m_taskId;
unsigned long m_renderId;
TRendererImpP m_rendererImp;
TFxPair m_fx;
TPointD m_framePos;
TDimension m_frameSize;
TRenderSettings m_info;
bool m_fieldRender, m_stereoscopic;
Mutex m_rasterGuard;
TTile m_tileA; //in normal and field rendering, Rendered at given frame; in stereoscopic, rendered left frame
TTile m_tileB; //in field rendering, rendered at frame + 0.5; in stereoscopic, rendered right frame
public:
RenderTask(unsigned long renderId, unsigned long taskId,
double frame, const TRenderSettings &ri, const TFxPair &fx,
const TPointD &framePos, const TDimension &frameSize,
const TRendererImpP &rendererImp);
~RenderTask() {}
void addFrame(double frame) { m_frames.push_back(frame); }
void buildTile(TTile &tile);
void releaseTiles();
void onFrameStarted();
void onFrameCompleted();
void onFrameFailed(TException &e);
void preRun();
void run();
int taskLoad() { return 100; }
void onFinished(TThread::RunnableP);
};
//================================================================================
// Implementations
//================================================================================
//==================
// TRenderer
//------------------
TRenderer::TRenderer(int nThread)
{
m_imp = new TRendererImp(nThread); //Already adds a ref
}
//---------------------------------------------------------
TRenderer::TRenderer(TRendererImp *imp)
: m_imp(imp)
{
if (m_imp)
m_imp->addRef();
}
//---------------------------------------------------------
TRenderer::TRenderer(const TRenderer &r)
: m_imp(r.m_imp)
{
if (m_imp)
m_imp->addRef();
}
//---------------------------------------------------------
void TRenderer::operator=(const TRenderer &r)
{
m_imp = r.m_imp;
if (m_imp)
m_imp->addRef();
}
//---------------------------------------------------------
TRenderer::~TRenderer()
{
if (m_imp)
m_imp->release();
}
//---------------------------------------------------------
//! The static method is intended for use inside a rendering thread only. It returns
//! a copy of the eventual TRenderer interface installed on the thread -
//! or an empty TRenderer if none happened to be. It can be set using the
//! install() and uninstall() methods.
TRenderer TRenderer::instance()
{
TRendererImp **lData = rendererStorage.localData();
if (lData)
return TRenderer(*lData);
return 0;
}
//---------------------------------------------------------
//! Returns the renderer id.
unsigned long TRenderer::rendererId()
{
return m_imp->m_rendererId;
}
//---------------------------------------------------------
//! Returns the rendering process id currently running on the invoking
//! thread.
unsigned long TRenderer::renderId()
{
unsigned long *lData = renderIdsStorage.localData();
return lData ? *lData : -1;
}
//---------------------------------------------------------
//! Builds and returns an id for starting a new rendering process.
unsigned long TRenderer::buildRenderId()
{
return TRendererImp::m_renderIdCounter++;
}
//---------------------------------------------------------
//! Returns the renderId that will be used by the next startRendering() call.
//! This method can be used to retrieve the renderId of a rendering instance
//! before it is actually started - provided that no other render instance
//! is launched inbetween.
unsigned long TRenderer::nextRenderId()
{
return TRendererImp::m_renderIdCounter;
}
//---------------------------------------------------------
inline void TRendererImp::declareRenderStart(unsigned long renderId)
{
//Inform the resource managers
for (unsigned int i = 0; i < m_managers.size(); ++i)
m_managers[i]->onRenderInstanceStart(renderId);
}
//! Declares that the render process of passed renderId is about to start.
void TRenderer::declareRenderStart(unsigned long renderId)
{
m_imp->declareRenderStart(renderId);
}
//---------------------------------------------------------
inline void TRendererImp::declareRenderEnd(unsigned long renderId)
{
//Inform the resource managers
for (int i = m_managers.size() - 1; i >= 0; --i)
m_managers[i]->onRenderInstanceEnd(renderId);
}
//! Declares that the render process of passed renderId has ended.
void TRenderer::declareRenderEnd(unsigned long renderId)
{
m_imp->declareRenderEnd(renderId);
}
//---------------------------------------------------------
inline void TRendererImp::declareFrameStart(double frame)
{
//Inform the resource managers
for (unsigned int i = 0; i < m_managers.size(); ++i)
m_managers[i]->onRenderFrameStart(frame);
}
//! Declares that render of passed frame is about to start.
void TRenderer::declareFrameStart(double frame)
{
m_imp->declareFrameStart(frame);
}
//---------------------------------------------------------
inline void TRendererImp::declareFrameEnd(double frame)
{
//Inform the resource managers
for (int i = m_managers.size() - 1; i >= 0; --i)
m_managers[i]->onRenderFrameEnd(frame);
}
//! Declares that render of passed has ended.
void TRenderer::declareFrameEnd(double frame)
{
m_imp->declareFrameEnd(frame);
}
//---------------------------------------------------------
inline void TRendererImp::declareStatusStart(int renderStatus)
{
//Inform the resource managers
for (unsigned int i = 0; i < m_managers.size(); ++i)
m_managers[i]->onRenderStatusStart(renderStatus);
}
//---------------------------------------------------------
inline void TRendererImp::declareStatusEnd(int renderStatus)
{
//Inform the resource managers
for (int i = m_managers.size() - 1; i >= 0; --i)
m_managers[i]->onRenderStatusEnd(renderStatus);
}
//---------------------------------------------------------
//! Installs the specified render process on the invoking thread.
void TRenderer::install(unsigned long renderId)
{
m_imp->addRef();
rendererStorage.setLocalData(new (TRendererImp *)(m_imp));
renderIdsStorage.setLocalData(new unsigned long(renderId));
}
//---------------------------------------------------------
//! Uninstalls any rendering process active on the invoking thread.
void TRenderer::uninstall()
{
rendererStorage.setLocalData(0);
renderIdsStorage.setLocalData(0);
m_imp->release();
}
//---------------------------------------------------------
TRenderResourceManager *TRenderer::getManager(unsigned int id) const
{
return m_imp->m_managers[id];
}
//---------------------------------------------------------
void TRenderer::enablePrecomputing(bool on)
{
m_imp->enablePrecomputing(on);
}
//---------------------------------------------------------
bool TRenderer::isPrecomputingEnabled() const
{
return m_imp->isPrecomputingEnabled();
}
//---------------------------------------------------------
void TRenderer::setThreadsCount(int nThreads)
{
m_imp->setThreadsCount(nThreads);
}
//---------------------------------------------------------
void TRenderer::addPort(TRenderPort *port)
{
m_imp->addPort(port);
}
//---------------------------------------------------------
void TRenderer::removePort(TRenderPort *port)
{
m_imp->removePort(port);
}
//---------------------------------------------------------
unsigned long TRenderer::startRendering(
double f,
const TRenderSettings &info,
const TFxPair &actualRoot)
{
assert(f >= 0);
std::vector<RenderData> *rds = new std::vector<RenderData>;
rds->push_back(RenderData(f, info, actualRoot));
return startRendering(rds);
}
//---------------------------------------------------------
//! Queues a rendering event in the main event loop.
//! NOTE: The passed pointer is owned by the TRenderer after it is submitted for rendering -
//! do not delete it later.
unsigned long TRenderer::startRendering(const std::vector<RenderData> *renderDatas)
{
if (renderDatas->empty()) {
delete renderDatas;
return -1;
}
//Build a new render Id
unsigned long renderId = m_imp->m_renderIdCounter++;
TRendererStartInvoker::StartInvokerRenderData srd;
srd.m_renderId = renderId;
srd.m_renderDataVector = renderDatas;
TRendererStartInvoker::instance()->emitStartRender(m_imp, srd);
return renderId;
}
//---------------------------------------------------------
void TRenderer::abortRendering(unsigned long renderId)
{
m_imp->abortRendering(renderId);
}
//---------------------------------------------------------
void TRenderer::stopRendering(bool waitForCompleteStop)
{
m_imp->stopRendering(waitForCompleteStop);
}
//---------------------------------------------------------
bool TRenderer::isAborted(unsigned long renderId) const
{
return m_imp->hasToDie(renderId);
}
//---------------------------------------------------------
int TRenderer::getRenderStatus(unsigned long renderId) const
{
return m_imp->getRenderStatus(renderId);
}
//================================================================================
//=====================
// TRendererImp
//---------------------
TRendererImp::TRendererImp(int nThreads)
: m_executor(), m_undoneTasks(), m_rendererId(m_rendererIdCounter++), m_precomputingEnabled(true)
{
m_executor.setMaxActiveTasks(nThreads);
std::vector<TRenderResourceManagerGenerator *> &generators =
TRenderResourceManagerGenerator::generators(false);
//May be adopted by other TRenderers from now on.
addRef();
rendererStorage.setLocalData(new (TRendererImp *)(this));
unsigned int i;
for (i = 0; i < generators.size(); ++i) {
TRenderResourceManager *manager = (*generators[i])();
if (manager)
m_managers.push_back(manager);
}
rendererStorage.setLocalData(0);
}
//---------------------------------------------------------
TRendererImp::~TRendererImp()
{
rendererStorage.setLocalData(new (TRendererImp *)(this));
int i;
for (i = m_managers.size() - 1; i >= 0; --i)
if (m_managers[i]->renderHasOwnership())
delete m_managers[i];
rendererStorage.setLocalData(0);
}
//---------------------------------------------------------
void TRendererImp::addPort(TRenderPort *port)
{
QWriteLocker sl(&m_portsLock);
PortContainerIterator it = std::find(m_ports.begin(), m_ports.end(), port);
if (it == m_ports.end())
m_ports.push_back(port);
}
//---------------------------------------------------------
void TRendererImp::removePort(TRenderPort *port)
{
QWriteLocker sl(&m_portsLock);
PortContainerIterator it = std::find(m_ports.begin(), m_ports.end(), port);
if (it != m_ports.end())
m_ports.erase(it);
}
//---------------------------------------------------------
bool TRendererImp::hasToDie(unsigned long renderId)
{
QMutexLocker sl(&m_renderInstancesMutex);
std::map<unsigned long, RenderInstanceInfos>::iterator it = m_activeInstances.find(renderId);
assert(it != m_activeInstances.end());
return it == m_activeInstances.end() ? true : it->second.m_canceled;
}
//---------------------------------------------------------
int TRendererImp::getRenderStatus(unsigned long renderId)
{
QMutexLocker sl(&m_renderInstancesMutex);
std::map<unsigned long, RenderInstanceInfos>::iterator it = m_activeInstances.find(renderId);
assert(it != m_activeInstances.end());
return it == m_activeInstances.end() ? true : it->second.m_status;
}
//---------------------------------------------------------
void TRendererImp::abortRendering(unsigned long renderId)
{
QMutexLocker sl(&m_renderInstancesMutex);
std::map<unsigned long, RenderInstanceInfos>::iterator it = m_activeInstances.find(renderId);
if (it != m_activeInstances.end())
it->second.m_canceled = true;
}
//---------------------------------------------------------
void TRendererImp::stopRendering(bool waitForCompleteStop)
{
QMutexLocker sl(&m_renderInstancesMutex);
{
//Tasks already stop rendering on their own when they don't find their render ids here.
std::map<unsigned long, RenderInstanceInfos>::iterator it;
for (it = m_activeInstances.begin(); it != m_activeInstances.end(); ++it)
it->second.m_canceled = true;
}
if (waitForCompleteStop && m_undoneTasks > 0) {
//Sometimes, QEventLoop suddenly stops processing slots (especially those notifications
//from active rendering instances) - therefore resulting in a block of the application.
//I've not figured out why (#QTBUG-11649?) - but substituting with a plain while
//seems to do the trick...
/*QEventLoop eventLoop;
m_waitingLoops.push_back(&eventLoop);
eventLoop.exec();*/
bool loopQuit = false;
m_waitingLoops.push_back(&loopQuit);
sl.unlock();
while (!loopQuit)
QCoreApplication::processEvents(QEventLoop::AllEvents | QEventLoop::WaitForMoreEvents);
}
}
//---------------------------------------------------------
void TRendererImp::quitWaitingLoops()
{
//Make the stopRendering waiting loops quit
while (!m_waitingLoops.empty()) {
//rendererImp->m_waitingLoops.back()->quit();
*m_waitingLoops.back() = true;
m_waitingLoops.pop_back();
}
}
//---------------------------------------------------------
void TRendererImp::notifyRasterStarted(const TRenderPort::RenderData &rd)
{
//Since notifications may trigger port removals, we always work on a copy of the ports
//vector.
TRendererImp::PortContainer portsCopy;
{
QReadLocker sl(&m_portsLock);
portsCopy = m_ports;
}
for (PortContainerIterator it = portsCopy.begin(); it != portsCopy.end(); ++it)
(*it)->onRenderRasterStarted(rd);
}
//---------------------------------------------------------
void TRendererImp::notifyRasterCompleted(const TRenderPort::RenderData &rd)
{
TRendererImp::PortContainer portsCopy;
{
QReadLocker sl(&m_portsLock);
portsCopy = m_ports;
}
assert(rd.m_rasA);
for (PortContainerIterator it = portsCopy.begin(); it != portsCopy.end(); ++it)
(*it)->onRenderRasterCompleted(rd);
}
//---------------------------------------------------------
void TRendererImp::notifyRasterFailure(const TRenderPort::RenderData &rd, TException &e)
{
TRendererImp::PortContainer portsCopy;
{
QReadLocker sl(&m_portsLock);
portsCopy = m_ports;
}
for (PortContainerIterator it = portsCopy.begin(); it != portsCopy.end(); ++it)
(*it)->onRenderFailure(rd, e);
}
//---------------------------------------------------------
void TRendererImp::notifyRenderFinished(bool isCanceled)
{
TRendererImp::PortContainer portsCopy;
{
QReadLocker sl(&m_portsLock);
portsCopy = m_ports;
}
auto sortedFxs = calculateSortedFxs(rootFx);
for (auto fx : sortedFxs) {
if (fx)
const_cast<TFx *>(fx)->callEndRenderHandler();
}
for (PortContainerIterator it = portsCopy.begin(); it != portsCopy.end(); ++it)
(*it)->onRenderFinished();
}
//================================================================================
//====================
// TRenderPort
//--------------------
TRenderPort::TRenderPort()
{
}
//---------------------------------------------------------
TRenderPort::~TRenderPort()
{
}
//---------------------------------------------------------
//!Setta \b m_renderArea a \b area e pulisce l'istanza corrente di \b RasterPool.
void TRenderPort::setRenderArea(const TRectD &area)
{
m_renderArea = area;
}
//---------------------------------------------------------
//!Ritorna \b m_renderArea.
TRectD &TRenderPort::getRenderArea()
{
return m_renderArea;
}
//================================================================================
//===================
// RenderTask
//-------------------
RenderTask::RenderTask(unsigned long renderId, unsigned long taskId,
double frame, const TRenderSettings &ri, const TFxPair &fx,
const TPointD &framePos, const TDimension &frameSize,
const TRendererImpP &rendererImp)
: m_renderId(renderId), m_taskId(taskId), m_info(ri), m_fx(fx), m_frameSize(frameSize), m_framePos(framePos), m_rendererImp(rendererImp), m_fieldRender(ri.m_fieldPrevalence != TRenderSettings::NoField), m_stereoscopic(ri.m_stereoscopic)
{
m_frames.push_back(frame);
//Connect the onFinished slot
connect(this, SIGNAL(finished(TThread::RunnableP)), this, SLOT(onFinished(TThread::RunnableP)));
connect(this, SIGNAL(exception(TThread::RunnableP)), this, SLOT(onFinished(TThread::RunnableP)));
//The shrink info is currently reversed to the settings'affine. Shrink info in the TRenderSettings
//is no longer supported.
m_info.m_shrinkX = m_info.m_shrinkY = 1;
}
//---------------------------------------------------------
void RenderTask::preRun()
{
TRectD geom(m_framePos, TDimensionD(m_frameSize.lx, m_frameSize.ly));
if (m_fx.m_frameA)
m_fx.m_frameA->dryCompute(geom, m_frames[0], m_info);
if (m_fx.m_frameB)
m_fx.m_frameB->dryCompute(geom, m_fieldRender ? m_frames[0] + 0.5 : m_frames[0], m_info);
}
//---------------------------------------------------------
void RenderTask::run()
{
//Retrieve the task's frame
assert(!m_frames.empty());
double t = m_frames[0];
if (m_rendererImp->hasToDie(m_renderId)) {
TException e("Render task aborted");
onFrameFailed(e);
return;
}
//Install the renderer in current thread
rendererStorage.setLocalData(new (TRendererImp *)(m_rendererImp.getPointer()));
renderIdsStorage.setLocalData(new unsigned long(m_renderId));
//Inform the managers of frame start
m_rendererImp->declareFrameStart(t);
auto sortedFxs = calculateSortedFxs(m_fx.m_frameA);
for (auto fx : sortedFxs) {
if (fx)
const_cast<TFx *>(fx)->callStartRenderFrameHandler(&m_info, t);
}
try {
onFrameStarted();
TStopWatch::global(8).start();
if (!m_fieldRender && !m_stereoscopic) {
//Common case - just build the first tile
buildTile(m_tileA);
/*-- 通常はここがFxのレンダリング処理 --*/
m_fx.m_frameA->compute(m_tileA, t, m_info);
} else {
assert(!(m_stereoscopic && m_fieldRender));
//Field rendering or stereoscopic case
if (m_stereoscopic) {
buildTile(m_tileA);
m_fx.m_frameA->compute(m_tileA, t, m_info);
buildTile(m_tileB);
m_fx.m_frameB->compute(m_tileB, t, m_info);
}
//if fieldPrevalence, Decide the rendering frames depending on field prevalence
else if (m_info.m_fieldPrevalence == TRenderSettings::EvenField) {
buildTile(m_tileA);
m_fx.m_frameA->compute(m_tileA, t, m_info);
buildTile(m_tileB);
m_fx.m_frameB->compute(m_tileB, t + 0.5, m_info);
} else {
buildTile(m_tileB);
m_fx.m_frameA->compute(m_tileB, t, m_info);
buildTile(m_tileA);
m_fx.m_frameB->compute(m_tileA, t + 0.5, m_info);
}
}
TStopWatch::global(8).stop();
onFrameCompleted();
} catch (TException &e) {
onFrameFailed(e);
} catch (...) {
TException ex("Unknown render exception");
onFrameFailed(ex);
}
//Inform the managers of frame end
m_rendererImp->declareFrameEnd(t);
//Uninstall the renderer from current thread
rendererStorage.setLocalData(0);
renderIdsStorage.setLocalData(0);
for (auto fx : sortedFxs) {
if (fx)
const_cast<TFx *>(fx)->callEndRenderFrameHandler(&m_info, t);
}
}
//---------------------------------------------------------
void RenderTask::buildTile(TTile &tile)
{
tile.m_pos = m_framePos;
tile.setRaster(m_rendererImp->m_rasterPool.getRaster(m_frameSize, m_info.m_bpp));
}
//---------------------------------------------------------
void RenderTask::releaseTiles()
{
m_rendererImp->m_rasterPool.releaseRaster(m_tileA.getRaster());
m_tileA.setRaster(TRasterP());
if (m_fieldRender || m_stereoscopic) {
m_rendererImp->m_rasterPool.releaseRaster(m_tileB.getRaster());
m_tileB.setRaster(TRasterP());
}
}
//---------------------------------------------------------
void RenderTask::onFrameStarted()
{
TRenderPort::RenderData rd(m_frames, m_info, 0, 0, m_renderId, m_taskId);
m_rendererImp->notifyRasterStarted(rd);
}
//---------------------------------------------------------
void RenderTask::onFrameCompleted()
{
TRasterP rasA(m_tileA.getRaster());
TRasterP rasB(m_tileB.getRaster());
if (m_fieldRender) {
assert(rasB);
double t = m_frames[0];
int f = (m_info.m_fieldPrevalence == TRenderSettings::EvenField) ? 0 : 1;
interlace(rasA, rasB, f);
rasB = TRasterP();
}
TRenderPort::RenderData rd(m_frames, m_info, rasA, rasB, m_renderId, m_taskId);
m_rendererImp->notifyRasterCompleted(rd);
}
//---------------------------------------------------------
void RenderTask::onFrameFailed(TException &e)
{
//TRasterP evenRas(m_evenTile.getRaster());
TRenderPort::RenderData rd(m_frames, m_info, m_tileA.getRaster(), m_tileB.getRaster(), m_renderId, m_taskId);
m_rendererImp->notifyRasterFailure(rd, e);
}
//---------------------------------------------------------
void RenderTask::onFinished(TThread::RunnableP)
{
TRendererImp *rendererImp = m_rendererImp.getPointer();
--rendererImp->m_undoneTasks;
//Tiles release back to the Raster Pool happens in the main thread, after all possible
//signals emitted in the onFrameCompleted/Failed notifications have been resolved, thus
//ensuring that no other rendering thread owns the rasters before them.
releaseTiles();
//Update the render instance status
bool instanceExpires = false;
{
QMutexLocker sl(&rendererImp->m_renderInstancesMutex);
std::map<unsigned long, TRendererImp::RenderInstanceInfos>::iterator it =
rendererImp->m_activeInstances.find(m_renderId);
if (it != rendererImp->m_activeInstances.end() && (--it->second.m_activeTasks) <= 0) {
instanceExpires = true;
rendererImp->m_activeInstances.erase(m_renderId);
}
}
//If the render instance has just expired
if (instanceExpires) {
/*-- キャンセルされた場合はm_overallRenderedRegionの更新をしない --*/
bool isCanceled = (m_info.m_isCanceled && *m_info.m_isCanceled);
//Inform the render ports
rendererImp->notifyRenderFinished(isCanceled);
//NOTE: This slot is currently invoked on the main thread. It could eventually be
//invoked directly on rendering threads, specifying the Qt::DirectConnection option -
//but probably there would be no real advantage in doing so...
//Temporarily install the renderer in current thread
rendererStorage.setLocalData(new (TRendererImp *)(rendererImp));
renderIdsStorage.setLocalData(new unsigned long(m_renderId));
//Inform the resource managers
rendererImp->declareRenderEnd(m_renderId);
//Uninstall the temporary
rendererStorage.setLocalData(0);
renderIdsStorage.setLocalData(0);
rendererImp->m_rasterPool.clear(); // Isn't this misplaced? Should be in the block
} // below...
//If no rendering task (of this or other render instances) is found...
if (rendererImp->m_undoneTasks == 0) {
QMutexLocker sl(&rendererImp->m_renderInstancesMutex);
rendererImp->quitWaitingLoops();
}
}
//================================================================================
// Tough Stuff
//================================================================================
void TRendererStartInvoker::emitStartRender(
TRendererImp *renderer, StartInvokerRenderData rd)
{
renderer->addRef();
Q_EMIT startRender(renderer, rd);
}
//---------------------------------------------------------
void TRendererStartInvoker::doStartRender(
TRendererImp *renderer, StartInvokerRenderData rd)
{
renderer->startRendering(rd.m_renderId, *rd.m_renderDataVector);
renderer->release();
delete rd.m_renderDataVector;
}
std::vector<const TFx *> calculateSortedFxs(TRasterFxP rootFx)
{
std::map<const TFx *, std::set<const TFx *>> E; /* 辺の情報 */
std::set<const TFx *> Sources; /* 入次数0のノード群 */
std::queue<const TFx *> Q;
Q.push(rootFx.getPointer());
E[rootFx.getPointer()] = std::set<const TFx *>();
while (!Q.empty()) {
const TFx *vptr = Q.front();
Q.pop();
if (!vptr) {
continue;
}
/* 繋がっている入力ポートの先の Fx を訪問する
入力ポートが無ければ終了 */
int portCount = vptr->getInputPortCount();
if (portCount < 1) {
Sources.insert(vptr);
continue;
}
for (int i = 0; i < portCount; i++) {
TFxPort *port = vptr->getInputPort(i);
if (!port) {
continue;
}
TFxP u = port->getFx();
const TFx *uptr = u.getPointer();
if (E.count(uptr) == 0) {
E[uptr] = std::set<const TFx *>();
}
if (E[uptr].count(vptr) == 0) {
E[uptr].insert(vptr);
}
Q.push(uptr);
}
}
/* トポロジカルソート */
std::set<const TFx *> visited;
std::vector<const TFx *> L;
std::function<void(const TFx *)> visit = [&visit, &visited, &E, &L](const TFx *fx) {
if (visited.count(fx))
return;
visited.insert(fx);
auto edge = E[fx];
for (auto i = edge.cbegin(); i != edge.cend(); i++) {
visit(*i);
}
L.insert(L.begin(), fx);
};
for (auto i = E.cbegin(); i != E.cend(); i++) {
visit(i->first);
}
return L;
}
//---------------------------------------------------------
void TRendererImp::startRendering(unsigned long renderId, const vector<TRenderer::RenderData> &renderDatas)
{
rootFx = renderDatas.front().m_fxRoot.m_frameA;
int T = renderDatas.size();
for (int z = 0; z < T; z++) {
auto sortedFxs = calculateSortedFxs(renderDatas[z].m_fxRoot.m_frameA);
if (z == 0) {
for (auto fx : sortedFxs) {
if (fx)
const_cast<TFx *>(fx)->callStartRenderHandler();
}
}
}
struct locals {
static inline void setStorage(TRendererImp *imp, unsigned long renderId)
{
rendererStorage.setLocalData(new (TRendererImp *)(imp));
renderIdsStorage.setLocalData(new unsigned long(renderId));
}
static inline void clearStorage()
{
rendererStorage.setLocalData(0);
renderIdsStorage.setLocalData(0);
}
static inline void declareStatusStart(
TRendererImp *imp, TRenderer::RenderStatus status,
RenderInstanceInfos *renderInfos)
{
renderInfos->m_status = status;
imp->declareStatusStart(status);
}
//-----------------------------------------------------------------------
struct InstanceDeclaration {
TRendererImp *m_imp;
unsigned long m_renderId;
bool m_rollback;
InstanceDeclaration(TRendererImp *imp, unsigned long renderId)
: m_imp(imp), m_renderId(renderId), m_rollback(true) {}
~InstanceDeclaration()
{
if (m_rollback) {
QMutexLocker locker(&m_imp->m_renderInstancesMutex);
m_imp->m_activeInstances.erase(m_renderId);
if (m_imp->m_undoneTasks == 0)
m_imp->quitWaitingLoops();
}
}
void commit() { m_rollback = false; }
};
struct StorageDeclaration {
StorageDeclaration(TRendererImp *imp, unsigned long renderId)
{
setStorage(imp, renderId);
}
~StorageDeclaration()
{
clearStorage();
}
};
struct RenderDeclaration {
TRendererImp *m_imp;
unsigned long m_renderId;
bool m_rollback;
RenderDeclaration(TRendererImp *imp, unsigned long renderId)
: m_imp(imp), m_renderId(renderId), m_rollback(true)
{
imp->declareRenderStart(renderId);
}
~RenderDeclaration()
{
if (m_rollback)
m_imp->declareRenderEnd(m_renderId);
}
void commit() { m_rollback = false; }
};
struct StatusDeclaration {
TRendererImp *m_imp;
TRenderer::RenderStatus m_status;
StatusDeclaration(TRendererImp *imp, TRenderer::RenderStatus status,
RenderInstanceInfos *renderInfos)
: m_imp(imp), m_status(status)
{
declareStatusStart(imp, status, renderInfos);
}
~StatusDeclaration()
{
m_imp->declareStatusEnd(m_status);
}
};
}; // locals
//DIAGNOSTICS_CLEAR;
//----------------------------------------------------------------------
// Preliminary initializations
//----------------------------------------------------------------------
// Calculate the overall render area - sum of all render ports' areas
TRectD renderArea;
{
QReadLocker sl(&m_portsLock);
for (PortContainerIterator it = m_ports.begin(); it != m_ports.end(); ++it)
renderArea += (*it)->getRenderArea();
}
const TRenderSettings &info(renderDatas[0].m_info);
//Extract the render geometry
TPointD pos(renderArea.getP00());
TDimension frameSize(tceil(renderArea.getLx()), tceil(renderArea.getLy()));
TRectD camBox(TPointD(pos.x / info.m_shrinkX, pos.y / info.m_shrinkY),
TDimensionD(frameSize.lx, frameSize.ly));
//Refresh the raster pool specs
m_rasterPool.setRasterSpecs(frameSize, info.m_bpp);
//Set a temporary active instance count - so that hasToDie(renderId) returns false
RenderInstanceInfos *renderInfos;
{
QMutexLocker locker(&m_renderInstancesMutex);
renderInfos = &m_activeInstances[renderId];
renderInfos->m_activeTasks = 1;
}
locals::InstanceDeclaration instanceDecl(this, renderId);
//----------------------------------------------------------------------
// Clustering - Render Tasks creation
//----------------------------------------------------------------------
std::vector<RenderTask *> tasksVector;
struct TasksCleaner {
std::vector<RenderTask *> &m_tasksVector;
~TasksCleaner()
{
std::for_each(m_tasksVector.begin(), m_tasksVector.end(),
tcg::deleter<RenderTask>());
}
} tasksCleaner = {tasksVector};
unsigned long tasksIdCounter = 0;
std::map<std::string, RenderTask *> clusters;
std::vector<TRenderer::RenderData>::const_iterator it;
std::map<std::string, RenderTask *>::iterator jt;
for (it = renderDatas.begin(); it != renderDatas.end(); ++it) {
// Check for user cancels
if (hasToDie(renderId))
return;
// Build the frame's description alias
const TRenderer::RenderData &renderData = *it;
/*--- カメラサイズ (LevelAutoやノイズで使用する) ---*/
TRenderSettings rs = renderData.m_info;
rs.m_cameraBox = camBox;
/*--- 途中でPreview計算がキャンセルされたときのフラグ ---*/
rs.m_isCanceled = &renderInfos->m_canceled;
TRasterFxP fx = renderData.m_fxRoot.m_frameA;
assert(fx);
double frame = renderData.m_frame;
string alias = fx->getAlias(frame, renderData.m_info);
if (renderData.m_fxRoot.m_frameB)
alias = alias + renderData.m_fxRoot.m_frameB->getAlias(frame, renderData.m_info);
// Search the alias among stored clusters - and store the frame
jt = clusters.find(alias);
if (jt == clusters.end()) {
RenderTask *newTask = new RenderTask(
renderId, tasksIdCounter++,
renderData.m_frame, rs, renderData.m_fxRoot,
pos, frameSize, this);
tasksVector.push_back(newTask);
clusters.insert(std::make_pair(alias, newTask));
} else
jt->second->addFrame(renderData.m_frame);
// Call processEvents to make the GUI reactive.
QCoreApplication::instance()->processEvents();
}
// Release the clusters - we'll just need the tasks vector from now on
clusters.clear();
std::vector<RenderTask *>::iterator kt, kEnd = tasksVector.end();
{
// Install TRenderer on current thread before proceeding
locals::StorageDeclaration storageDecl(this, renderId);
// Inform the resource managers
locals::RenderDeclaration renderDecl(this, renderId);
//----------------------------------------------------------------------
// Precomputing
//----------------------------------------------------------------------
if (m_precomputingEnabled) {
//Set current maxTileSize for cache manager precomputation
const TRenderSettings &rs = renderDatas[0].m_info;
TPredictiveCacheManager::instance()->setMaxTileSize(rs.m_maxTileSize);
TPredictiveCacheManager::instance()->setBPP(rs.m_bpp);
//Perform the first precomputing run - fx usages declaration
{
locals::StatusDeclaration firstrunDecl(this, TRenderer::FIRSTRUN, renderInfos);
for (kt = tasksVector.begin(); kt != kEnd; ++kt) {
if (hasToDie(renderId))
return;
(*kt)->preRun();
//NOTE: Thread-specific data must be temporarily uninstalled before
//processing events (which may redefine the thread data).
locals::clearStorage();
QCoreApplication::instance()->processEvents();
locals::setStorage(this, renderId);
}
}
//Pass to the TESTRUN status - this one should faithfully reproduce
//the actual COMPUTING status
{
locals::StatusDeclaration testrunDecl(this, TRenderer::TESTRUN, renderInfos);
for (kt = tasksVector.begin(); kt != kEnd; ++kt) {
if (hasToDie(renderId))
return;
(*kt)->preRun();
//NOTE: Thread-specific data must be temporarily uninstalled before
//processing events (which may redefine the thread data).
locals::clearStorage();
QCoreApplication::instance()->processEvents();
locals::setStorage(this, renderId);
}
}
}
//----------------------------------------------------------------------
// Render
//----------------------------------------------------------------------
locals::declareStatusStart(this, TRenderer::COMPUTING, renderInfos);
// Update the tasks counts
m_undoneTasks += tasksVector.size();
{
QMutexLocker locker(&m_renderInstancesMutex);
renderInfos->m_activeTasks = tasksVector.size();
}
renderDecl.commit(); // Declarations are taken over by render tasks
}
instanceDecl.commit(); // Same here
// Launch the render
for (kt = tasksVector.begin(); kt != tasksVector.end(); ++kt)
m_executor.addTask(*kt);
tasksVector.clear(); // Prevent tasks destruction by TasksCleaner
}
void TRenderer::initialize()
{
TRendererStartInvoker::instance();
}