Currently job artifacts in CI/CD pipelines on LRZ GitLab never expire. Starting from Wed 26.1.2022 the default expiration time will be 30 days (GitLab default). Currently existing 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 57e41972 authored by schultezub's avatar schultezub
Browse files

Started implementing Job-Pattern for OpenGL operations

 * introducing OpenGLJobProcessor (the name somehow sucks...)
 * renamed JobPool to PriorityPool (still needs some work)
 * AbstractPipeline::executeProcessor() now takes a pointer as argument (necessary for job pattern)

git-svn-id: https://camplinux.in.tum.de/svn/campvis/trunk@251 bb408c1c-ae56-11e1-83d9-df6b3e0c105e
parent 0eb22d19
......@@ -41,8 +41,8 @@ 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("SliceVis", new SliceVis());
//app.addVisualizationPipeline("DVRVis", new DVRVis());
app.init();
int toReturn = app.run();
......
......@@ -42,6 +42,7 @@
#include "application/tumvispainter.h"
#include "application/gui/mainwindow.h"
#include "core/tools/opengljobprocessor.h"
#include "core/pipeline/abstractpipeline.h"
#include "core/pipeline/visualizationpipeline.h"
#include "core/pipeline/pipelineevaluator.h"
......@@ -64,6 +65,8 @@ namespace TUMVis {
_mainWindow = new MainWindow(this);
tgt::QtContextManager::init();
OpenGLJobProcessor::init();
}
TumVisApplication::~TumVisApplication() {
......@@ -134,12 +137,15 @@ namespace TUMVis {
it->second->init();
}
GLJobProc.start();
_initialized = true;
}
void TumVisApplication::deinit() {
tgtAssert(_initialized, "Tried to deinitialize uninitialized TumVisApplication.");
GLJobProc.stop();
{
// Deinit everything OpenGL related using the local context.
tgt::GLContextScopedLock lock(_localContext->getContext());
......@@ -162,6 +168,8 @@ namespace TUMVis {
}
}
OpenGLJobProcessor::deinit();
tgt::QtContextManager::deinit();
tgt::deinit();
......
......@@ -38,6 +38,8 @@
#include "core/datastructures/imagedatarendertarget.h"
#include "core/pipeline/visualizationpipeline.h"
#include "core/tools/job.h"
#include "core/tools/opengljobprocessor.h"
namespace TUMVis {
const std::string TumVisPainter::loggerCat_ = "TUMVis.core.TumVisPainter";
......@@ -69,11 +71,14 @@ namespace TUMVis {
std::unique_lock<tbb::mutex> lock(CtxtMgr.getGlMutex());
while (! _stopExecution) {
getCanvas()->getContext()->acquire();
if (_dirty)
GLJobProc.enqueueJob(getCanvas(), new CallMemberFuncJob<TumVisPainter>(this, &TumVisPainter::paint), Normal);
/*getCanvas()->getContext()->acquire();
paint();
getCanvas()->swap();
getCanvas()->swap();*/
while (!_stopExecution && !_dirty)
//while (!_stopExecution)
_renderCondition.wait(lock);
}
......@@ -140,6 +145,8 @@ namespace TUMVis {
}
LGL_ERROR;
}
getCanvas()->swap();
}
void TumVisPainter::sizeChanged(const tgt::ivec2& size) {
......
......@@ -84,10 +84,12 @@ namespace TUMVis {
return _data;
}
void AbstractPipeline::executeProcessor(AbstractProcessor& processor) {
processor.lockProperties();
processor.process(_data);
processor.unlockProperties();
void AbstractPipeline::executeProcessor(AbstractProcessor* processor) {
tgtAssert(processor != 0, "Processor must not be 0.");
processor->lockProperties();
processor->process(_data);
processor->unlockProperties();
}
InvalidationLevel& AbstractPipeline::getInvalidationLevel() {
......
......@@ -128,7 +128,7 @@ namespace TUMVis {
* Executes the processor \a processor on the pipeline's data and locks its properties meanwhile.
* \param processor Processor to execute.
*/
void executeProcessor(AbstractProcessor& processor);
void executeProcessor(AbstractProcessor* processor);
DataContainer _data; ///< DataContainer containing local working set of data for this Pipeline
std::vector<AbstractProcessor*> _processors; ///< List of all processors of this pipeline
......
......@@ -31,6 +31,8 @@
#include "tgt/glcanvas.h"
#include "tgt/glcontext.h"
#include "core/datastructures/imagedatarendertarget.h"
#include "core/tools/job.h"
#include "core/tools/opengljobprocessor.h"
namespace TUMVis {
const std::string VisualizationPipeline::loggerCat_ = "TUMVis.core.datastructures.VisualizationPipeline";
......@@ -94,12 +96,16 @@ namespace TUMVis {
_renderTargetSize.setValue(size);
}
void VisualizationPipeline::lockGLContextAndExecuteProcessor(AbstractProcessor& processor) {
void VisualizationPipeline::lockGLContextAndExecuteProcessor(AbstractProcessor* processor) {
tgtAssert(_canvas != 0, "Set a valid canvas before calling this method!");
tgt::GLContextScopedLock lock(_canvas->getContext());
GLJobProc.enqueueJob(
_canvas,
new CallMemberFunc1ArgJob<VisualizationPipeline, AbstractProcessor*>(this, &VisualizationPipeline::executeProcessor, processor),
Normal);
/*tgt::GLContextScopedLock lock(_canvas->getContext());
executeProcessor(processor);
glFinish(); // TODO: is glFlush enough or do we need a glFinish here?
LGL_ERROR;
LGL_ERROR;*/
}
void VisualizationPipeline::setCanvas(tgt::GLCanvas* canvas) {
......
......@@ -143,7 +143,7 @@ namespace TUMVis {
* and locks its properties meanwhile.
* \param processor Processor to execute.
*/
void lockGLContextAndExecuteProcessor(AbstractProcessor& processor);
void lockGLContextAndExecuteProcessor(AbstractProcessor* processor);
GenericProperty<tgt::ivec2> _renderTargetSize; ///< Viewport size of target canvas
StringProperty _renderTargetID; ///< ID of the render target image to be rendered to the canvas
......
......@@ -56,7 +56,7 @@ namespace TUMVis {
* Specific job, that is calling a member function pasing no argument.
*/
template<class T>
class CallMemberFuncJob {
class CallMemberFuncJob : public AbstractJob {
public:
/**
* Creates an new job, that is calling \a callee on \a target pasing no argument.
......@@ -92,7 +92,7 @@ namespace TUMVis {
* Specific job, that is calling a member function pasing a single argument.
*/
template<class T, class A1>
class CallMemberFunc1ArgJob {
class CallMemberFunc1ArgJob : public AbstractJob {
public:
/**
* Creates an new job, that is calling \a callee on \a target pasing \a arg1 as single argument.
......@@ -100,7 +100,7 @@ namespace TUMVis {
* \param callee Member function to call
* \param arg1 Argument to pass to \a callee
*/
CallMemberFunc1ArgJob(T* target, void (T::*callee)(A1), const A1& arg1)
CallMemberFunc1ArgJob(T* target, void (T::*callee)(A1), A1 arg1)
: _target(target)
, _callee(callee)
, _arg1(arg1)
......@@ -124,7 +124,7 @@ namespace TUMVis {
protected:
T* _target; ///< Target object
void (T::*_callee)(A1); /// <Member function to call
const A1& _arg1; ///< Argument to pass to \a callee
A1 _arg1; ///< Argument to pass to \a callee
};
}
......
......@@ -28,50 +28,7 @@
#include "jobpool.h"
#include "tgt/assert.h"
#include "core/tools/job.h"
namespace TUMVis {
const size_t JobPool::NUM_PRIORITIES = 3;
JobPool::JobPool()
: _queues(0)
{
_queues = new tbb::concurrent_queue<AbstractJob*>[NUM_PRIORITIES];
}
JobPool::~JobPool() {
// delete jobs
AbstractJob* toDelete = 0;
for (size_t i = 0; i < NUM_PRIORITIES; ++i) {
while (_queues[i].try_pop(toDelete))
delete toDelete;
}
// delete queues
delete[] _queues;
}
void JobPool::enqueueJob(AbstractJob* job, JobPriority priority) {
size_t i = static_cast<size_t>(priority);
tgtAssert(i < NUM_PRIORITIES, "Job priority index must be lower than the total number or priorities.");
tgtAssert(job != 0, "Job must not be 0");
_queues[i].push(job);
s_enqueuedJob();
}
AbstractJob* JobPool::dequeueJob() {
// very simple scheduling algorithm. This should be made fairer and avoid starving!
AbstractJob* toReturn = 0;
for (size_t i = 0; i < NUM_PRIORITIES; ++i) {
if (_queues[i].try_pop(toReturn));
return toReturn;
}
return 0;
}
}
......@@ -37,57 +37,106 @@ namespace TUMVis {
class AbstractJob;
/**
* A JobPool manages multible Jobs in queues with different priorities.
* 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 jobs.
* \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. :)
*/
class JobPool {
template<class T>
class PriorityPool {
public:
/**
* Enumeration of the different priorities of jobs.
*/
enum JobPriority {
Realtime = 0, ///< Realtime jobs are always considered first during dequeueing.
Normal = 1, ///< Jobs with normal priorities are dequeued as soon as there are no realtime jobs left
Low = 2 ///< Low priority jobs are only considered if there are no jobs in the queue with higher priority
};
/**
* Creates a new JobPool
* Creates a new PriorityPool
*/
JobPool();
PriorityPool();
/**
* Destructor, deletes all jobs which are still enqueued.
* Destructor, deletes all items which are still enqueued.
*/
~JobPool();
~PriorityPool();
/**
* Enqueues the given Job with the given priority.
*
* \note JobPool takes ownership of \a job.
* \param job Job to enqueue, JobPool takes ownership of this Job!
* \param priority Priority of the job to enqueue
* \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(AbstractJob* job, JobPriority priority);
void enqueueJob(T* item, PriorityPoolPriority priority);
/**
* Dequeues the next job according to the scheduling strategy.
* \note The calling function takes the ownership of the returned job!
* 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 job to execute, 0 if there is currently no job to execute. The caller takes ownership of the job!
* \return The next item to execute, 0 if there is currently no item to execute. The caller takes ownership of the item!
*/
AbstractJob* dequeueJob();
T* dequeueJob();
/// Signal being emitted, when a job has been enqueued.
/// 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 JobPriority enum.
tbb::concurrent_queue<AbstractJob*>* _queues; ///< Array of job queues, one for each JobPriority
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
// ================================================================================================
//
// 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 Universitt Mnchen
// Boltzmannstr. 3, 85748 Garching b. Mnchen, 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 "opengljobprocessor.h"
#include "tgt/assert.h"
#include "tgt/qt/qtcontextmanager.h"
#include "core/tools/job.h"
namespace TUMVis {
OpenGLJobProcessor::OpenGLJobProcessor()
: _currentContext(0)
{
_jobPool.s_enqueuedJob.connect(this, &OpenGLJobProcessor::OnEnqueuedJob);
}
OpenGLJobProcessor::~OpenGLJobProcessor() {
_jobPool.s_enqueuedJob.disconnect(this);
}
void OpenGLJobProcessor::stop() {
_stopExecution = true;
_evaluationCondition.notify_all();
Runnable::stop();
}
void OpenGLJobProcessor::run() {
std::unique_lock<tbb::mutex> lock(CtxtMgr.getGlMutex());
while (! _stopExecution) {
OpenGLJob* job = 0;
while (job = _jobPool.dequeueJob()) {
if (_currentContext != job->_canvas) {
if (_currentContext != 0) {
glFinish();
LGL_ERROR;
}
job->_canvas->getContext()->acquire();
_currentContext = job->_canvas;
}
job->_job->execute();
delete job->_job;
delete job;
}
// while (! _stopExecution && _jobPool.empty())
_evaluationCondition.wait(lock);
}
// 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::OnEnqueuedJob() {
_evaluationCondition.notify_all();
}
}
// ================================================================================================
//
// 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 Universitt Mnchen
// Boltzmannstr. 3, 85748 Garching b. Mnchen, 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 OPENGLJOBPROCESSOR_H__
#define OPENGLJOBPROCESSOR_H__
#include "sigslot/sigslot.h"
#include "tgt/singleton.h"
#include "tbb/include/tbb/atomic.h"
#include "tbb/include/tbb/compat/condition_variable"
#include "core/tools/jobpool.h"
#include "core/tools/runnable.h"
namespace tgt {
class GLCanvas;
}
namespace TUMVis {
/**
*
* \todo Check if the inheritance of PriorityPool is a cool design or not...
*/
class OpenGLJobProcessor : public tgt::Singleton<OpenGLJobProcessor>, public Runnable, public sigslot::has_slots<> {
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
};
virtual ~OpenGLJobProcessor();
/// \see Runnable::stop
void stop();
/**
* Performs the pipeline evaluation using conditional wait.
* \sa Runnable::run
*/
void run();
/**
* Enqueues the given Job with the given priority.
*
* \note OpenGLJobProcessor takes ownership of \a job.
* \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);
/**
* Slot to be called by _jobPool::s_enqueuedJob signal.
*/
void OnEnqueuedJob();
protected:
OpenGLJobProcessor();
PriorityPool<OpenGLJob> _jobPool; ///< PriorityPool to process
std::condition_variable _evaluationCondition; ///< conditional wait to be used when there are currently no jobs to process
tgt::GLCanvas* _currentContext; ///< current active OpenGL context
};
}
#define GLJobProc tgt::Singleton<OpenGLJobProcessor>::getRef()
#endif // OPENGLJOBPROCESSOR_H__
......@@ -53,7 +53,6 @@ namespace TUMVis {
}
void ReferenceCounted::removeReference() {
// TODO: I'm afraid this is not 100% thread-safe - refCount might change between atomic decrement, check and deletion...
if (--_refCount == 0)
delete this;
}
......
......@@ -135,7 +135,7 @@ namespace kisscl {
std::pair<Context*, Device*> p = std::make_pair(context, device);
auto lb = _commandQueues.lower_bound(p);
std::map< std::pair<Context*, Device*>, CommandQueue*>::iterator lb = _commandQueues.lower_bound(p);
if (lb == _commandQueues.end() || lb ->first != p) {
CommandQueue* queue = new CommandQueue(context, device, _commandQueueProperties);
_commandQueues.insert(lb, std::make_pair(p, queue));
......
......@@ -113,7 +113,7 @@ namespace TUMVis {
// TODO: think whether we want to lock all processors already here.
}
if (! _imageReader.getInvalidationLevel().isValid()) {
executeProcessor(_imageReader);
executeProcessor(&_imageReader);
// convert data
DataContainer::ScopedTypedData<ImageData> img(_data, "reader.output");
......@@ -135,16 +135,16 @@ namespace TUMVis {
}
}
if (! _eepGenerator.getInvalidationLevel().isValid()) {
lockGLContextAndExecuteProcessor(_eepGenerator);
lockGLContextAndExecuteProcessor(&_eepGenerator);
}
if (! _eepGenerator.getInvalidationLevel().isValid() || !_drrraycater.getInvalidationLevel().isValid()) {
lockGLContextAndExecuteProcessor(_drrraycater);
lockGLContextAndExecuteProcessor(&_drrraycater);
}
if (! _eepGenerator.getInvalidationLevel().isValid() || !_simpleRaycaster.getInvalidationLevel().isValid()) {
lockGLContextAndExecuteProcessor(_simpleRaycaster);
lockGLContextAndExecuteProcessor(&_simpleRaycaster);