The expiration time for new job artifacts in CI/CD pipelines is now 30 days (GitLab default). Previously generated artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

Commit 5d20ee70 authored by schultezub's avatar schultezub
Browse files

* 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; ...@@ -42,7 +42,7 @@ using namespace TUMVis;
int main(int argc, char** argv) { int main(int argc, char** argv) {
TumVisApplication app(argc, argv); TumVisApplication app(argc, argv);
app.addVisualizationPipeline("SliceVis", new SliceVis()); app.addVisualizationPipeline("SliceVis", new SliceVis());
//app.addVisualizationPipeline("DVRVis", new DVRVis()); app.addVisualizationPipeline("DVRVis", new DVRVis());
app.init(); app.init();
int toReturn = app.run(); int toReturn = app.run();
......
...@@ -191,9 +191,9 @@ namespace TUMVis { ...@@ -191,9 +191,9 @@ namespace TUMVis {
for (std::vector<PipelineEvaluator*>::iterator it = _pipelineEvaluators.begin(); it != _pipelineEvaluators.end(); ++it) { for (std::vector<PipelineEvaluator*>::iterator it = _pipelineEvaluators.begin(); it != _pipelineEvaluators.end(); ++it) {
(*it)->start(); (*it)->start();
} }
for (std::vector< std::pair<VisualizationPipeline*, TumVisPainter*> >::iterator it = _visualizations.begin(); it != _visualizations.end(); ++it) { // for (std::vector< std::pair<VisualizationPipeline*, TumVisPainter*> >::iterator it = _visualizations.begin(); it != _visualizations.end(); ++it) {
it->second->start(); // it->second->start();
} // }
// Start QApplication // Start QApplication
int toReturn = QApplication::exec(); int toReturn = QApplication::exec();
...@@ -202,9 +202,9 @@ namespace TUMVis { ...@@ -202,9 +202,9 @@ namespace TUMVis {
for (std::vector<PipelineEvaluator*>::iterator it = _pipelineEvaluators.begin(); it != _pipelineEvaluators.end(); ++it) { for (std::vector<PipelineEvaluator*>::iterator it = _pipelineEvaluators.begin(); it != _pipelineEvaluators.end(); ++it) {
(*it)->stop(); (*it)->stop();
} }
for (std::vector< std::pair<VisualizationPipeline*, TumVisPainter*> >::iterator it = _visualizations.begin(); it != _visualizations.end(); ++it) { // for (std::vector< std::pair<VisualizationPipeline*, TumVisPainter*> >::iterator it = _visualizations.begin(); it != _visualizations.end(); ++it) {
it->second->stop(); // it->second->stop();
} // }
return toReturn; return toReturn;
} }
...@@ -226,18 +226,18 @@ namespace TUMVis { ...@@ -226,18 +226,18 @@ namespace TUMVis {
// create canvas and painter for the VisPipeline and connect all together // create canvas and painter for the VisPipeline and connect all together
tgt::QtThreadedCanvas* canvas = CtxtMgr.createContext(name, "TUMVis", tgt::ivec2(512, 512)); tgt::QtThreadedCanvas* canvas = CtxtMgr.createContext(name, "TUMVis", tgt::ivec2(512, 512));
GLJobProc.registerContext(canvas);
canvas->init(); canvas->init();
TumVisPainter* painter = new TumVisPainter(canvas, vp); TumVisPainter* painter = new TumVisPainter(canvas, vp);
canvas->setPainter(painter, false); canvas->setPainter(painter, false);
CtxtMgr.releaseCurrentContext();
_visualizations.push_back(std::make_pair(vp, painter)); _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)? vp->setCanvas(canvas);
tgt::QtThreadedCanvas* evaluationContext = CtxtMgr.createContext(name + "_eval", "", tgt::ivec2(512, 512));
vp->setCanvas(evaluationContext);
addPipeline(vp); addPipeline(vp);
CtxtMgr.releaseCurrentContext();
} }
} }
\ No newline at end of file
...@@ -71,8 +71,6 @@ namespace TUMVis { ...@@ -71,8 +71,6 @@ namespace TUMVis {
std::unique_lock<tbb::mutex> lock(CtxtMgr.getGlMutex()); std::unique_lock<tbb::mutex> lock(CtxtMgr.getGlMutex());
while (! _stopExecution) { while (! _stopExecution) {
if (_dirty)
GLJobProc.enqueueJob(getCanvas(), new CallMemberFuncJob<TumVisPainter>(this, &TumVisPainter::paint), Normal);
/*getCanvas()->getContext()->acquire(); /*getCanvas()->getContext()->acquire();
paint(); paint();
...@@ -90,61 +88,57 @@ namespace TUMVis { ...@@ -90,61 +88,57 @@ namespace TUMVis {
if (getCanvas() == 0) if (getCanvas() == 0)
return; return;
while (_dirty) { const tgt::ivec2& size = getCanvas()->getSize();
_dirty = false; glViewport(0, 0, size.x, size.y);
const tgt::ivec2& size = getCanvas()->getSize(); // try get Data
glViewport(0, 0, size.x, size.y); DataContainer::ScopedTypedData<ImageDataRenderTarget> image(_pipeline->getDataContainer(), _pipeline->getRenderTargetID());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// try get Data if (image != 0) {
DataContainer::ScopedTypedData<ImageDataRenderTarget> image(_pipeline->getDataContainer(), _pipeline->getRenderTargetID()); // activate shader
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _copyShader->activate();
if (image != 0) { _copyShader->setIgnoreUniformLocationError(true);
// activate shader _copyShader->setUniform("_viewportSize", size);
_copyShader->activate(); _copyShader->setUniform("_viewportSizeRCP", 1.f / tgt::vec2(size));
_copyShader->setIgnoreUniformLocationError(true); _copyShader->setIgnoreUniformLocationError(false);
_copyShader->setUniform("_viewportSize", size);
_copyShader->setUniform("_viewportSizeRCP", 1.f / tgt::vec2(size)); // bind input textures
_copyShader->setIgnoreUniformLocationError(false); tgt::TextureUnit colorUnit, depthUnit;
image->bind(_copyShader, &colorUnit, &depthUnit);
// 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; 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(); getCanvas()->swap();
} }
...@@ -190,12 +184,7 @@ namespace TUMVis { ...@@ -190,12 +184,7 @@ namespace TUMVis {
} }
void TumVisPainter::onRenderTargetChanged() { void TumVisPainter::onRenderTargetChanged() {
// TODO: What happens, if the mutex is still acquired? GLJobProc.enqueueJob(getCanvas(), new CallMemberFuncJob<TumVisPainter>(this, &TumVisPainter::paint), OpenGLJobProcessor::PaintJob);
// Will the render thread woken up as soon as it is released?
if (!_stopExecution) {
_dirty = true;
_renderCondition.notify_all();
}
} }
void TumVisPainter::setCanvas(tgt::GLCanvas* canvas) { void TumVisPainter::setCanvas(tgt::GLCanvas* canvas) {
......
...@@ -87,9 +87,11 @@ namespace TUMVis { ...@@ -87,9 +87,11 @@ namespace TUMVis {
void AbstractPipeline::executeProcessor(AbstractProcessor* processor) { void AbstractPipeline::executeProcessor(AbstractProcessor* processor) {
tgtAssert(processor != 0, "Processor must not be 0."); tgtAssert(processor != 0, "Processor must not be 0.");
processor->lockProperties(); if (! processor->getInvalidationLevel().isValid()) {
processor->process(_data); processor->lockProperties();
processor->unlockProperties(); processor->process(_data);
processor->unlockProperties();
}
} }
InvalidationLevel& AbstractPipeline::getInvalidationLevel() { InvalidationLevel& AbstractPipeline::getInvalidationLevel() {
......
...@@ -101,7 +101,7 @@ namespace TUMVis { ...@@ -101,7 +101,7 @@ namespace TUMVis {
GLJobProc.enqueueJob( GLJobProc.enqueueJob(
_canvas, _canvas,
new CallMemberFunc1ArgJob<VisualizationPipeline, AbstractProcessor*>(this, &VisualizationPipeline::executeProcessor, processor), new CallMemberFunc1ArgJob<VisualizationPipeline, AbstractProcessor*>(this, &VisualizationPipeline::executeProcessor, processor),
Normal); OpenGLJobProcessor::SerialJob);
/*tgt::GLContextScopedLock lock(_canvas->getContext()); /*tgt::GLContextScopedLock lock(_canvas->getContext());
executeProcessor(processor); executeProcessor(processor);
glFinish(); // TODO: is glFlush enough or do we need a glFinish here? glFinish(); // TODO: is glFlush enough or do we need a glFinish here?
......
...@@ -35,6 +35,9 @@ ...@@ -35,6 +35,9 @@
namespace TUMVis { namespace TUMVis {
/**
* \todo Implement correct behavior if the TF changes during locked property state.
*/
class TransferFunctionProperty : public AbstractProperty, public sigslot::has_slots<> { class TransferFunctionProperty : public AbstractProperty, public sigslot::has_slots<> {
public: 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 { ...@@ -36,12 +36,12 @@ namespace TUMVis {
OpenGLJobProcessor::OpenGLJobProcessor() OpenGLJobProcessor::OpenGLJobProcessor()
: _currentContext(0) : _currentContext(0)
, _startTimeCurrentContext(0)
{ {
_jobPool.s_enqueuedJob.connect(this, &OpenGLJobProcessor::OnEnqueuedJob);
} }
OpenGLJobProcessor::~OpenGLJobProcessor() { OpenGLJobProcessor::~OpenGLJobProcessor() {
_jobPool.s_enqueuedJob.disconnect(this);
} }
void OpenGLJobProcessor::stop() { void OpenGLJobProcessor::stop() {
...@@ -55,36 +55,119 @@ namespace TUMVis { ...@@ -55,36 +55,119 @@ namespace TUMVis {
std::unique_lock<tbb::mutex> lock(CtxtMgr.getGlMutex()); std::unique_lock<tbb::mutex> lock(CtxtMgr.getGlMutex());
while (! _stopExecution) { while (! _stopExecution) {
OpenGLJob* job = 0; // this is a simple round-robing scheduling between all contexts:
while (job = _jobPool.dequeueJob()) { bool hadWork = false;
if (_currentContext != job->_canvas) { 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) { if (_currentContext != 0) {
glFinish(); glFinish();
LGL_ERROR; LGL_ERROR;
} }
job->_canvas->getContext()->acquire(); context->getContext()->acquire();
_currentContext = job->_canvas; _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(); // third: execute paint job
delete job->_job; if ((jobToDo = a->second->_paintJob) != 0) {
delete job; 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); _evaluationCondition.wait(lock);
_currentContext->getContext()->acquire();
}
} }
// release OpenGL context, so that other threads can access it // release OpenGL context, so that other threads can access it
CtxtMgr.releaseCurrentContext(); CtxtMgr.releaseCurrentContext();
} }
void OpenGLJobProcessor::enqueueJob(tgt::GLCanvas* canvas, AbstractJob* job, PriorityPoolPriority priority) { void OpenGLJobProcessor::enqueueJob(tgt::GLCanvas* canvas, AbstractJob* job, JobType priority) {
_jobPool.enqueueJob(new OpenGLJob(job, canvas), 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() {