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

opengljobprocessor.cpp 9.73 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
75
76
77
78
79
80
81
    }

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

194
195
196
197
198
    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:
199
200
201
202
203
204
                {
                    AbstractJob* oldJob = a->second->_paintJob.fetch_and_store(job);
                    if (oldJob != 0)
                        delete oldJob;
                    break;
                }
205
206
207
208
209
210
211
212
213
214
215
216
            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 {
217
            tgtAssert(false, "Specified context not found. Contexts must be registered before they can have jobs.");
218
        }
219
220
221

        _evaluationCondition.notify_all();
    }
222
223

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

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

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

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

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

261
262


263
264
}