Commit 5d20ee70 authored by schultezub's avatar schultezub

* completely rewrote OpenGLJobProcessor now offering a fancy scheduling strategy

 * started converting old multi-threaded OpenGL code to new job-concept
 * painter and evaluator of a VisualizationPipeline now share the same OpenGL context

git-svn-id: https://camplinux.in.tum.de/svn/campvis/trunk@252 bb408c1c-ae56-11e1-83d9-df6b3e0c105e
parent 57e41972
......@@ -42,7 +42,7 @@ using namespace TUMVis;
int main(int argc, char** argv) {
TumVisApplication app(argc, argv);
app.addVisualizationPipeline("SliceVis", new SliceVis());
//app.addVisualizationPipeline("DVRVis", new DVRVis());
app.addVisualizationPipeline("DVRVis", new DVRVis());
app.init();
int toReturn = app.run();
......
......@@ -191,9 +191,9 @@ namespace TUMVis {
for (std::vector<PipelineEvaluator*>::iterator it = _pipelineEvaluators.begin(); it != _pipelineEvaluators.end(); ++it) {
(*it)->start();
}
for (std::vector< std::pair<VisualizationPipeline*, TumVisPainter*> >::iterator it = _visualizations.begin(); it != _visualizations.end(); ++it) {
it->second->start();
}
// for (std::vector< std::pair<VisualizationPipeline*, TumVisPainter*> >::iterator it = _visualizations.begin(); it != _visualizations.end(); ++it) {
// it->second->start();
// }
// Start QApplication
int toReturn = QApplication::exec();
......@@ -202,9 +202,9 @@ namespace TUMVis {
for (std::vector<PipelineEvaluator*>::iterator it = _pipelineEvaluators.begin(); it != _pipelineEvaluators.end(); ++it) {
(*it)->stop();
}
for (std::vector< std::pair<VisualizationPipeline*, TumVisPainter*> >::iterator it = _visualizations.begin(); it != _visualizations.end(); ++it) {
it->second->stop();
}
// for (std::vector< std::pair<VisualizationPipeline*, TumVisPainter*> >::iterator it = _visualizations.begin(); it != _visualizations.end(); ++it) {
// it->second->stop();
// }
return toReturn;
}
......@@ -226,18 +226,18 @@ namespace TUMVis {
// create canvas and painter for the VisPipeline and connect all together
tgt::QtThreadedCanvas* canvas = CtxtMgr.createContext(name, "TUMVis", tgt::ivec2(512, 512));
GLJobProc.registerContext(canvas);
canvas->init();
TumVisPainter* painter = new TumVisPainter(canvas, vp);
canvas->setPainter(painter, false);
CtxtMgr.releaseCurrentContext();
_visualizations.push_back(std::make_pair(vp, painter));
// TODO: is there a more leightweight method to create a context for the pipeline (just performing off-screen rendering)?
tgt::QtThreadedCanvas* evaluationContext = CtxtMgr.createContext(name + "_eval", "", tgt::ivec2(512, 512));
vp->setCanvas(evaluationContext);
vp->setCanvas(canvas);
addPipeline(vp);
CtxtMgr.releaseCurrentContext();
}
}
\ No newline at end of file
......@@ -71,8 +71,6 @@ namespace TUMVis {
std::unique_lock<tbb::mutex> lock(CtxtMgr.getGlMutex());
while (! _stopExecution) {
if (_dirty)
GLJobProc.enqueueJob(getCanvas(), new CallMemberFuncJob<TumVisPainter>(this, &TumVisPainter::paint), Normal);
/*getCanvas()->getContext()->acquire();
paint();
......@@ -90,61 +88,57 @@ namespace TUMVis {
if (getCanvas() == 0)
return;
while (_dirty) {
_dirty = false;
const tgt::ivec2& size = getCanvas()->getSize();
glViewport(0, 0, size.x, size.y);
// try get Data
DataContainer::ScopedTypedData<ImageDataRenderTarget> image(_pipeline->getDataContainer(), _pipeline->getRenderTargetID());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (image != 0) {
// activate shader
_copyShader->activate();
_copyShader->setIgnoreUniformLocationError(true);
_copyShader->setUniform("_viewportSize", size);
_copyShader->setUniform("_viewportSizeRCP", 1.f / tgt::vec2(size));
_copyShader->setIgnoreUniformLocationError(false);
// bind input textures
tgt::TextureUnit colorUnit, depthUnit;
image->bind(_copyShader, &colorUnit, &depthUnit);
LGL_ERROR;
// execute the shader
tgt::QuadRenderer::renderQuad();
_copyShader->deactivate();
LGL_ERROR;
}
else {
// TODO: render some nifty error texture
// so long, we do some dummy rendering
tgt::Camera c(tgt::vec3(0.f,0.f,2.f));
c.look();
glColor3f(1.f, 0.f, 0.f);
tgt::Sphere sphere(.5f, 64, 32);
sphere.render();
/*
// render error texture
if (!errorTex_) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
return;
}
glClear(GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
errorTex_->bind();
errorTex_->enable();
glColor3f(1.f, 1.f, 1.f);
renderQuad();
errorTex_->disable();*/
}
const tgt::ivec2& size = getCanvas()->getSize();
glViewport(0, 0, size.x, size.y);
// try get Data
DataContainer::ScopedTypedData<ImageDataRenderTarget> image(_pipeline->getDataContainer(), _pipeline->getRenderTargetID());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (image != 0) {
// activate shader
_copyShader->activate();
_copyShader->setIgnoreUniformLocationError(true);
_copyShader->setUniform("_viewportSize", size);
_copyShader->setUniform("_viewportSizeRCP", 1.f / tgt::vec2(size));
_copyShader->setIgnoreUniformLocationError(false);
// bind input textures
tgt::TextureUnit colorUnit, depthUnit;
image->bind(_copyShader, &colorUnit, &depthUnit);
LGL_ERROR;
// execute the shader
tgt::QuadRenderer::renderQuad();
_copyShader->deactivate();
LGL_ERROR;
}
else {
// TODO: render some nifty error texture
// so long, we do some dummy rendering
tgt::Camera c(tgt::vec3(0.f,0.f,2.f));
c.look();
glColor3f(1.f, 0.f, 0.f);
tgt::Sphere sphere(.5f, 64, 32);
sphere.render();
/*
// render error texture
if (!errorTex_) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
return;
}
glClear(GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
errorTex_->bind();
errorTex_->enable();
glColor3f(1.f, 1.f, 1.f);
renderQuad();
errorTex_->disable();*/
}
LGL_ERROR;
getCanvas()->swap();
}
......@@ -190,12 +184,7 @@ namespace TUMVis {
}
void TumVisPainter::onRenderTargetChanged() {
// TODO: What happens, if the mutex is still acquired?
// Will the render thread woken up as soon as it is released?
if (!_stopExecution) {
_dirty = true;
_renderCondition.notify_all();
}
GLJobProc.enqueueJob(getCanvas(), new CallMemberFuncJob<TumVisPainter>(this, &TumVisPainter::paint), OpenGLJobProcessor::PaintJob);
}
void TumVisPainter::setCanvas(tgt::GLCanvas* canvas) {
......
......@@ -87,9 +87,11 @@ namespace TUMVis {
void AbstractPipeline::executeProcessor(AbstractProcessor* processor) {
tgtAssert(processor != 0, "Processor must not be 0.");
processor->lockProperties();
processor->process(_data);
processor->unlockProperties();
if (! processor->getInvalidationLevel().isValid()) {
processor->lockProperties();
processor->process(_data);
processor->unlockProperties();
}
}
InvalidationLevel& AbstractPipeline::getInvalidationLevel() {
......
......@@ -101,7 +101,7 @@ namespace TUMVis {
GLJobProc.enqueueJob(
_canvas,
new CallMemberFunc1ArgJob<VisualizationPipeline, AbstractProcessor*>(this, &VisualizationPipeline::executeProcessor, processor),
Normal);
OpenGLJobProcessor::SerialJob);
/*tgt::GLContextScopedLock lock(_canvas->getContext());
executeProcessor(processor);
glFinish(); // TODO: is glFlush enough or do we need a glFinish here?
......
......@@ -35,6 +35,9 @@
namespace TUMVis {
/**
* \todo Implement correct behavior if the TF changes during locked property state.
*/
class TransferFunctionProperty : public AbstractProperty, public sigslot::has_slots<> {
public:
/**
......
// ================================================================================================
//
// This file is part of the TUMVis Visualization Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012, all rights reserved,
// Christian Schulte zu Berge (christian.szb@in.tum.de)
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
//
// The licensing of this softare is not yet resolved. Until then, redistribution in source or
// binary forms outside the CAMP chair is not permitted, unless explicitly stated in legal form.
// However, the names of the original authors and the above copyright notice must retain in its
// original state in any case.
//
// Legal disclaimer provided by the BSD license:
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// ================================================================================================
#include "jobpool.h"
namespace TUMVis {
}
// ================================================================================================
//
// This file is part of the TUMVis Visualization Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012, all rights reserved,
// Christian Schulte zu Berge (christian.szb@in.tum.de)
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
//
// The licensing of this softare is not yet resolved. Until then, redistribution in source or
// binary forms outside the CAMP chair is not permitted, unless explicitly stated in legal form.
// However, the names of the original authors and the above copyright notice must retain in its
// original state in any case.
//
// Legal disclaimer provided by the BSD license:
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// ================================================================================================
#ifndef JOBPOOL_H__
#define JOBPOOL_H__
#include "sigslot/sigslot.h"
#include "tbb/include/tbb/concurrent_queue.h"
namespace TUMVis {
class AbstractJob;
/**
* Enumeration of the different priorities of items.
*/
enum PriorityPoolPriority {
Realtime = 0, ///< Realtime items are always considered first during dequeueing.
Normal = 1, ///< Items with normal priorities are dequeued as soon as there are no realtime items left
Low = 2 ///< Low priority items are only considered if there are no items in the queue with higher priority
};
/**
* A PriorityPool manages multiple items of type T in queues with different priorities.
* Similar to a priority queue but different...
*
* \note This class is to be considered as thread-safe.
* \todo Implement a suitable scheduling strategy to avoid starving of low priority items.
* This sounds like a good opportunity to take a look at the Betriebssysteme lecture slides. :)
*/
template<class T>
class PriorityPool {
public:
/**
* Creates a new PriorityPool
*/
PriorityPool();
/**
* Destructor, deletes all items which are still enqueued.
*/
~PriorityPool();
/**
* Enqueues the given Job with the given priority.
*
* \note PriorityPool takes ownership of \a item.
* \param item Item to enqueue, PriorityPool takes ownership of this Job!
* \param priority Priority of the item to enqueue
*/
void enqueueJob(T* item, PriorityPoolPriority priority);
/**
* Dequeues the next item according to the scheduling strategy.
* \note The calling function takes the ownership of the returned item!
* \todo Develop a good scheduling strategy and describe it here.
* \return The next item to execute, 0 if there is currently no item to execute. The caller takes ownership of the item!
*/
T* dequeueJob();
/// Signal being emitted, when a item has been enqueued.
sigslot::signal0<> s_enqueuedJob;
protected:
static const size_t NUM_PRIORITIES; ///< total number of piorities, make sure that this matches the Priority enum.
tbb::concurrent_queue<T*>* _queues; ///< Array of item queues, one for each Priority
};
// ================================================================================================
template<class T>
const size_t TUMVis::PriorityPool<T>::NUM_PRIORITIES = 3;
template<class T>
TUMVis::PriorityPool<T>::PriorityPool()
{
_queues = new tbb::concurrent_queue<T*>[NUM_PRIORITIES];
}
template<class T>
TUMVis::PriorityPool<T>::~PriorityPool() {
// delete jobs
T* toDelete = 0;
for (size_t i = 0; i < NUM_PRIORITIES; ++i) {
while (_queues[i].try_pop(toDelete))
delete toDelete;
}
// delete queues
delete[] _queues;
}
template<class T>
void TUMVis::PriorityPool<T>::enqueueJob(T* item, PriorityPoolPriority priority) {
size_t i = static_cast<size_t>(priority);
tgtAssert(i < NUM_PRIORITIES, "Item priority index must be lower than the total number or priorities.");
tgtAssert(item != 0, "Item must not be 0");
_queues[i].push(item);
s_enqueuedJob();
}
template<class T>
T* TUMVis::PriorityPool<T>::dequeueJob() {
// very simple scheduling algorithm. This should be made fairer and avoid starving!
T* toReturn = 0;
for (size_t i = 0; i < NUM_PRIORITIES; ++i) {
if (_queues[i].try_pop(toReturn))
return toReturn;
}
return 0;
}
}
#endif // JOBPOOL_H__
\ No newline at end of file
......@@ -36,12 +36,12 @@ namespace TUMVis {
OpenGLJobProcessor::OpenGLJobProcessor()
: _currentContext(0)
, _startTimeCurrentContext(0)
{
_jobPool.s_enqueuedJob.connect(this, &OpenGLJobProcessor::OnEnqueuedJob);
}
OpenGLJobProcessor::~OpenGLJobProcessor() {
_jobPool.s_enqueuedJob.disconnect(this);
}
void OpenGLJobProcessor::stop() {
......@@ -55,36 +55,119 @@ namespace TUMVis {
std::unique_lock<tbb::mutex> lock(CtxtMgr.getGlMutex());
while (! _stopExecution) {
OpenGLJob* job = 0;
while (job = _jobPool.dequeueJob()) {
if (_currentContext != job->_canvas) {
// this is a simple round-robing scheduling between all contexts:
bool hadWork = false;
clock_t maxTimePerContext = 30 / _contexts.size();
for (size_t i = 0; i < _contexts.size(); ++i) {
_startTimeCurrentContext = clock() * 1000 / CLOCKS_PER_SEC;
tgt::GLCanvas* context = _contexts[i];
// std::map<tgt::GLCanvas*, PerContextJobQueue*>::const_iterator a = _contextQueueMap.find(context);
// if (a == _contextQueueMap.end()) {
tbb::concurrent_hash_map<tgt::GLCanvas*, PerContextJobQueue*>::const_accessor a;
if (!_contextQueueMap.find(a, context)) {
tgtAssert(false, "Should not reach this: Did not find context in contextQueueMap!");
break;
}
if (a->second->empty())
continue;
if (_currentContext != context) {
if (_currentContext != 0) {
glFinish();
LGL_ERROR;
}
job->_canvas->getContext()->acquire();
_currentContext = job->_canvas;
context->getContext()->acquire();
_currentContext = context;
}
// now comes the per-context scheduling strategy:
// first: perform as much serial jobs as possible:
AbstractJob* jobToDo = 0;
while ((clock() * 1000 / CLOCKS_PER_SEC) - _startTimeCurrentContext < maxTimePerContext) {
// try fetch a job
if (! a->second->_serialJobs.try_pop(jobToDo)) {
// no job to do, exit the while loop
break;
}
// execute and delete the job
jobToDo->execute();
delete jobToDo;
}
// second: execute one low-prio job if existant
if (a->second->_lowPriorityJobs.try_pop(jobToDo)) {
jobToDo->execute();
delete jobToDo;
}
job->_job->execute();
delete job->_job;
delete job;
// third: execute paint job
if ((jobToDo = a->second->_paintJob) != 0) {
jobToDo->execute();
delete jobToDo;
a->second->_paintJob = 0;
}
// update hadWork flag
hadWork = (jobToDo != 0);
}
// while (! _stopExecution && _jobPool.empty())
if (! hadWork) {
CtxtMgr.releaseCurrentContext();
_evaluationCondition.wait(lock);
_currentContext->getContext()->acquire();
}
}
// release OpenGL context, so that other threads can access it
CtxtMgr.releaseCurrentContext();
}
void OpenGLJobProcessor::enqueueJob(tgt::GLCanvas* canvas, AbstractJob* job, PriorityPoolPriority priority) {
_jobPool.enqueueJob(new OpenGLJob(job, canvas), priority);
}
void OpenGLJobProcessor::enqueueJob(tgt::GLCanvas* canvas, AbstractJob* job, JobType priority) {
// find the corresponding JobQueue for the context and update it:
//std::map<tgt::GLCanvas*, PerContextJobQueue*>::const_iterator a = _contextQueueMap.find(canvas);
tbb::concurrent_hash_map<tgt::GLCanvas*, PerContextJobQueue*>::const_accessor a;
if (_contextQueueMap.find(a, canvas)) {
switch (priority) {
case PaintJob:
if (a->second->_paintJob != 0)
delete a->second->_paintJob;
a->second->_paintJob = job;
break;
case SerialJob:
a->second->_serialJobs.push(job);
break;
case LowPriorityJob:
a->second->_lowPriorityJobs.push(job);
break;
default:
tgtAssert(false, "Should not reach this - wrong job type!");
break;
}
}
else {
tgtAssert(false, "Specified context not found. Context must be registered before they can have jobs.");
}
void OpenGLJobProcessor::OnEnqueuedJob() {
_evaluationCondition.notify_all();
}
void OpenGLJobProcessor::registerContext(tgt::GLCanvas* context) {
#ifdef TUMVIS_DEBUG
//tbb::concurrent_hash_map<tgt::GLCanvas*, PerContextJobQueue*>::const_accessor a;
// std::map<tgt::GLCanvas*, PerContextJobQueue*>::iterator a = _contextQueueMap.find(context);
// if (a != _contextQueueMap.end())
tbb::concurrent_hash_map<tgt::GLCanvas*, PerContextJobQueue*>::const_accessor a;
if (_contextQueueMap.find(a, context))
tgtAssert(false, "Contexts shall only be registered once!");
#endif
PerContextJobQueue* newQueue = new PerContextJobQueue;
_contextQueueMap.insert(std::make_pair(context, newQueue));
_contexts.push_back(context);
}
}
......@@ -32,10 +32,16 @@
#include "sigslot/sigslot.h"
#include "tgt/singleton.h"
#include "tbb/include/tbb/atomic.h"
#include "tbb/include/tbb/concurrent_queue.h"
#include "tbb/include/tbb/concurrent_hash_map.h"
#include "tbb/include/tbb/concurrent_vector.h"
#include "tbb/include/tbb/compat/condition_variable"
#include "core/tools/jobpool.h"
#include "core/tools/job.h"
#include "core/tools/runnable.h"
#include <ctime>
namespace tgt {
class GLCanvas;
}
......@@ -49,19 +55,20 @@ namespace TUMVis {
friend class tgt::Singleton<OpenGLJobProcessor>;
public:
struct OpenGLJob {
OpenGLJob(AbstractJob* job, tgt::GLCanvas* canvas)
: _job(job)
, _canvas(canvas)
{}
AbstractJob* _job; ///< Job to execute
tgt::GLCanvas* _canvas; ///< OpenGL context to execute job in
/**
* Enumeration of the different priorities of items.
*/
enum JobType {
PaintJob, ///< PaintJobs have the highest priority
SerialJob, ///< SerialJobs have a lower priority than PaintJobs, but are guaranteed to be executed in order.
LowPriorityJob ///< Low priority jobs have the lowest priority, can be executed at any time. The only guarantee is that thay won't starve.
};
virtual ~OpenGLJobProcessor();
void registerContext(tgt::GLCanvas* context);
/// \see Runnable::stop
void stop();
......@@ -78,19 +85,34 @@ namespace TUMVis {
* \param job Job to enqueue, PriorityPool takes ownership of this Job!
* \param priority Priority of the job to enqueue
*/
void enqueueJob(tgt::GLCanvas* canvas, AbstractJob* job, PriorityPoolPriority priority);
void enqueueJob(tgt::GLCanvas* canvas, AbstractJob* job, JobType priority);