// ================================================================================================ // // This file is part of the CAMPVis Software Framework. // // If not explicitly stated otherwise: Copyright (C) 2012, all rights reserved, // Christian Schulte zu Berge // Chair for Computer Aided Medical Procedures // Technische Universität München // Boltzmannstr. 3, 85748 Garching b. München, Germany // For a full list of authors and contributors, please refer to the file "AUTHORS.txt". // // 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/logmanager.h" #include "tgt/openglgarbagecollector.h" #include "tgt/glcontextmanager.h" #include "core/tools/job.h" namespace campvis { OpenGLJobProcessor::OpenGLJobProcessor() : _currentContext(0) { _pause = 0; } OpenGLJobProcessor::~OpenGLJobProcessor() { // delete all per-context job queues and unfinished jobs for (tbb::concurrent_vector::const_iterator it = _contexts.begin(); it != _contexts.end(); ++it) { tbb::concurrent_hash_map::const_accessor a; if (_contextQueueMap.find(a, *it)) { delete a->second; } } _contextQueueMap.clear(); _contexts.clear(); } void OpenGLJobProcessor::stop() { _stopExecution = true; _evaluationCondition.notify_all(); } void OpenGLJobProcessor::run() { std::unique_lock lock(tgt::GlContextManager::getRef().getGlMutex()); clock_t lastCleanupTime = clock() * 1000 / CLOCKS_PER_SEC; while (! _stopExecution) { // this is a simple round-robing scheduling between all contexts: bool hadWork = false; // TODO: consider only non-empty context queues here clock_t maxTimePerContext = static_cast(30 / _contexts.size()); for (size_t i = 0; i < _contexts.size(); ++i) { clock_t startTimeCurrentContext = clock() * 1000 / CLOCKS_PER_SEC; tgt::GLCanvas* context = _contexts[i]; tbb::concurrent_hash_map::const_accessor a; if (!_contextQueueMap.find(a, context)) { tgtAssert(false, "Should not reach this: Did not find context in contextQueueMap!"); break; } // avoid expensive context-switches for contexts without pending jobs. if (a->second->empty()) continue; // we will have work, so update the flag hadWork = true; // perform context switch if necessary if (_currentContext != context) { if (_currentContext != 0) { glFinish(); LGL_ERROR; } tgt::GlContextManager::getRef().acquireContext(context); _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; } // third: execute paint job jobToDo = a->second->_paintJob.fetch_and_store(0); if (jobToDo != 0) { jobToDo->execute(); delete jobToDo; } // fourth: start the GC if it's time if (clock() * 1000 / CLOCKS_PER_SEC - lastCleanupTime > 250) { GLGC.deleteGarbage(); lastCleanupTime = clock(); } } while (_pause > 0) { GLGC.deleteGarbage(); lastCleanupTime = clock(); tgt::GlContextManager::getRef().releaseCurrentContext(); _evaluationCondition.wait(lock); tgt::GlContextManager::getRef().acquireContext(_currentContext); hadWork = true; } if (! hadWork) { if (_currentContext != 0) { GLGC.deleteGarbage(); lastCleanupTime = clock(); } tgt::GlContextManager::getRef().releaseCurrentContext(); _evaluationCondition.wait(lock); tgt::GlContextManager::getRef().acquireContext(_currentContext); } } // release OpenGL context, so that other threads can access it tgt::GlContextManager::getRef().releaseCurrentContext(); } void OpenGLJobProcessor::pause() { ++_pause; } void OpenGLJobProcessor::resume() { if (_pause == 0) { tgtAssert(false, "Called resume on non-paused job processor!"); return; } --_pause; if (_pause == 0) _evaluationCondition.notify_all(); } void OpenGLJobProcessor::enqueueJob(tgt::GLCanvas* canvas, AbstractJob* job, JobType priority) { tbb::concurrent_hash_map::const_accessor a; if (_contextQueueMap.find(a, canvas)) { switch (priority) { case PaintJob: { AbstractJob* oldJob = a->second->_paintJob.fetch_and_store(job); if (oldJob != 0) delete oldJob; 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. Contexts must be registered before they can have jobs."); } _evaluationCondition.notify_all(); } void OpenGLJobProcessor::registerContext(tgt::GLCanvas* context) { #ifdef CAMPVIS_DEBUG tbb::concurrent_hash_map::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); } void OpenGLJobProcessor::deregisterContext(tgt::GLCanvas* context) { tbb::concurrent_hash_map::const_accessor a; if (_contextQueueMap.find(a, context)) { delete a->second; _contextQueueMap.erase(a); } } tgt::GLCanvas* OpenGLJobProcessor::iKnowWhatImDoingGetArbitraryContext() { if (_currentContext != 0) return _currentContext; else if (!_contexts.empty()) return _contexts.front(); else { tgtAssert(false, "No Contexts registered!"); return 0; } } }