21.10.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

opengljobprocessor.cpp 9.76 KB
Newer Older
1
2
// ================================================================================================
// 
schultezub's avatar
schultezub committed
3
// This file is part of the CAMPVis Software Framework.
4
// 
5
// If not explicitly stated otherwise: Copyright (C) 2012-2014, all rights reserved,
schultezub's avatar
schultezub committed
6
//      Christian Schulte zu Berge <christian.szb@in.tum.de>
7
//      Chair for Computer Aided Medical Procedures
8
9
//      Technische Universitaet Muenchen
//      Boltzmannstr. 3, 85748 Garching b. Muenchen, Germany
10
// 
schultezub's avatar
schultezub committed
11
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
12
// 
13
14
15
16
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 
// except in compliance with the License. You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
17
// 
18
19
20
21
// Unless required by applicable law or agreed to in writing, software distributed under the 
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
// either express or implied. See the License for the specific language governing permissions 
// and limitations under the License.
22
23
24
25
26
27
// 
// ================================================================================================

#include "opengljobprocessor.h"

#include "tgt/assert.h"
28
29
#include "tgt/logmanager.h"
#include "tgt/openglgarbagecollector.h"
30
#include "tgt/glcontextmanager.h"
31
32
#include "core/tools/job.h"

schultezub's avatar
schultezub committed
33
namespace campvis {
34

35
36
    std::thread::id OpenGLJobProcessor::_this_thread_id;

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// ================================================================================================

    OpenGLJobProcessor::ScopedSynchronousGlJobExecution::ScopedSynchronousGlJobExecution()
        : _lock(nullptr)
    {
        if (! GLJobProc.isCurrentThreadOpenGlThread()) {
            GLJobProc.pause();
            _lock = new tgt::GLContextScopedLock(GLJobProc.iKnowWhatImDoingGetArbitraryContext());
        }
    }

    OpenGLJobProcessor::ScopedSynchronousGlJobExecution::~ScopedSynchronousGlJobExecution() {
        if (_lock != nullptr) {
            delete _lock;
            GLJobProc.resume();
        }
    }

// ================================================================================================
    
57
58
    OpenGLJobProcessor::OpenGLJobProcessor()
    {
59
        _pause = 0;
60
        _currentContext = 0;
61
62
63
    }

    OpenGLJobProcessor::~OpenGLJobProcessor() {
schultezub's avatar
schultezub committed
64
65
66
67
68
69
70
        // delete all per-context job queues and unfinished jobs
        for (tbb::concurrent_vector<tgt::GLCanvas*>::const_iterator it = _contexts.begin(); it != _contexts.end(); ++it) {
            tbb::concurrent_hash_map<tgt::GLCanvas*, PerContextJobQueue*>::const_accessor a;
            if (_contextQueueMap.find(a, *it)) {
                delete a->second;
            }
        }
71

schultezub's avatar
schultezub committed
72
73
        _contextQueueMap.clear();
        _contexts.clear();
74
        _jobPool.recycle();
75
76
77
78
79
80
81
82
    }

    void OpenGLJobProcessor::stop() {
        _stopExecution = true;
        _evaluationCondition.notify_all();
    }

    void OpenGLJobProcessor::run() {
83
84
        _this_thread_id = std::this_thread::get_id();

85
        std::unique_lock<std::mutex> lock(tgt::GlContextManager::getRef().getGlMutex());
86
        clock_t lastCleanupTime = clock() * 1000 / CLOCKS_PER_SEC;
87
88

        while (! _stopExecution) {
89
90
            // this is a simple round-robing scheduling between all contexts:
            bool hadWork = false;
schultezub's avatar
schultezub committed
91
            // TODO: consider only non-empty context queues here
92
            clock_t maxTimePerContext = static_cast<clock_t>(30 / _contexts.size());
93
94

            for (size_t i = 0; i < _contexts.size(); ++i) {
95
                clock_t startTimeCurrentContext = clock() * 1000 / CLOCKS_PER_SEC;
96
97
98
99
                tgt::GLCanvas* context = _contexts[i];

                tbb::concurrent_hash_map<tgt::GLCanvas*, PerContextJobQueue*>::const_accessor a;
                if (!_contextQueueMap.find(a, context)) {
100
101
                    //tgtAssert(false, "Should not reach this: Did not find context in contextQueueMap!");
                    continue;
102
103
                }

104
                // avoid expensive context-switches for contexts without pending jobs.
105
106
107
                if (a->second->empty())
                    continue;

108
109
110
                // we will have work, so update the flag
                hadWork = true;

schultezub's avatar
schultezub committed
111
                // perform context switch if necessary
112
                if (_currentContext != context) {
113
114
115
116
                    if (_currentContext != 0) {
                        glFinish();
                        LGL_ERROR;
                    }
117
                    tgt::GlContextManager::getRef().acquireContext(context);
118
119
120
121
122
123
                    _currentContext = context;
                }

                // now comes the per-context scheduling strategy:
                // first: perform as much serial jobs as possible:
                AbstractJob* jobToDo = 0;
124
                while ((clock() * 1000 / CLOCKS_PER_SEC) - startTimeCurrentContext < maxTimePerContext) {
125
126
127
128
129
130
131
132
133
134
135
136
137
138
                    // 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;
139
140
                }

141
                // third: execute paint job
142
143
                jobToDo = a->second->_paintJob.fetch_and_store(0);
                if (jobToDo != 0) {
144
145
146
                    jobToDo->execute();
                    delete jobToDo;
                }
147
148
149
150
151
152
153


                // fourth: start the GC if it's time
                if (clock() * 1000 / CLOCKS_PER_SEC - lastCleanupTime > 250) {
                    GLGC.deleteGarbage();
                    lastCleanupTime = clock();
                }
154
            }
155
156
            
            while (_pause > 0) {
157
158
                GLGC.deleteGarbage();
                lastCleanupTime = clock();
159
                tgt::GlContextManager::getRef().releaseCurrentContext();
160
                _evaluationCondition.wait(lock);
161
                tgt::GlContextManager::getRef().acquireContext(_currentContext);
162
163
                hadWork = true;
            }
164

165
            if (! hadWork) {
166
167
168
169
                if (_currentContext != 0) {
                    GLGC.deleteGarbage();
                    lastCleanupTime = clock();
                }
170
                tgt::GlContextManager::getRef().releaseCurrentContext();
171
                _evaluationCondition.wait(lock);
172
                tgt::GlContextManager::getRef().acquireContext(_currentContext);
173
            }
174
175
176
        }

        // release OpenGL context, so that other threads can access it
177
        tgt::GlContextManager::getRef().releaseCurrentContext();
178
179
    }

180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
    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();
    }

195
196
197
198
199
    void OpenGLJobProcessor::enqueueJob(tgt::GLCanvas* canvas, AbstractJob* job, JobType priority) {
        tbb::concurrent_hash_map<tgt::GLCanvas*, PerContextJobQueue*>::const_accessor a;
        if (_contextQueueMap.find(a, canvas)) {
            switch (priority) {
            case PaintJob:
200
201
202
203
204
205
                {
                    AbstractJob* oldJob = a->second->_paintJob.fetch_and_store(job);
                    if (oldJob != 0)
                        delete oldJob;
                    break;
                }
206
207
208
209
210
211
212
213
214
215
216
217
            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 {
218
            tgtAssert(false, "Specified context not found. Contexts must be registered before they can have jobs.");
219
        }
220
221
222

        _evaluationCondition.notify_all();
    }
223
224

    void OpenGLJobProcessor::registerContext(tgt::GLCanvas* context) {
schultezub's avatar
schultezub committed
225
#ifdef CAMPVIS_DEBUG
226
227
228
229
230
231
232
233
234
235
        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);
    }

236
    void OpenGLJobProcessor::deregisterContext(tgt::GLCanvas* context) {
237
        tbb::concurrent_hash_map<tgt::GLCanvas*, PerContextJobQueue*>::accessor a;
238
239
240
241
        if (_contextQueueMap.find(a, context)) {
            delete a->second;
            _contextQueueMap.erase(a);
        }
242
243
244

        if (_currentContext == context)
            _currentContext.compare_and_swap(0, context);
245
246
    }

247
248
249
250
251
252
253
254
255
256
257
    tgt::GLCanvas* OpenGLJobProcessor::iKnowWhatImDoingGetArbitraryContext() {
        if (_currentContext != 0)
            return _currentContext;
        else if (!_contexts.empty())
            return _contexts.front();
        else {
            tgtAssert(false, "No Contexts registered!");
            return 0;
        }
    }

258
259
260
261
    bool OpenGLJobProcessor::isCurrentThreadOpenGlThread() const {
        return std::this_thread::get_id() == _this_thread_id;
    }

262
263


264
265
}