Nun könnte man sagen: Na klar, ist ja auch so einfach. Genau! Und damit
sich keiner mit einer so einfachen Sache aufhalten muss, kommt hier das
Bespiel:
Dieses Beispiel ist eine Kombination aus zwei bereits existierenden. Das
erste ist ein kleines Programm aus dem Buch C++ GUI Programming with Qt
3 von Jasmin Blanchette und Mark Summerfield (Kapitel 8; Programm:
cube). Es demonstriert GL-Picking mit einem QGLWidget. Der Code
kann hier
heruntergeladen werden. Das zweite Beispiel ist der schon erwähnte Qt
Quarterly Artikel Glimpsing the Third Dimension.
Das Wichtigste vom Quelltext im Überblick
main.cpp
35 #ifdef Q_WS_X11
36 #include <X11/Xlib.h> // for XInitThreads() call
37 #endif
...
43 #ifdef Q_WS_X11
44 // this needs to be the first in the app to make Xlib calls thread save
45 // needed for OpenGl rendering threads
46 XInitThreads();
47 #endif
Dieser Header ist unter X11 wichtig um Xlib-Aufrufe mit XInitThreads()
thread-safe machen zu können. Der Rest des Codes in main.cpp ist der
normale Code für die Initialisierung einer minimalen Qt-Anwendung.
exampleglwidget.cpp
39 ExampleGLWidget::ExampleGLWidget(QWidget *parent, const char *name)
40 : QGLWidget(parent, name),
41 glt(*this)
42
43 {
44 setFormat(QGLFormat(DoubleBuffer | DepthBuffer));
45
46 // Buffer swap is handled in the rendering thread
47 setAutoBufferSwap(false);
48
49 // start the rendering thread
50 initRendering();
51 }
Hier ist der Konstruktor der QGLWidget Subklasse. Es wird der
OpenGL-Rendering-Thread glt mit einer Referenz auf das
ExampleGLWidget initialisiert. Desweitern wird das automatische
Umschalten des Buffers abgeschaltet, da dies im Render-Thread geschieht.
Der Aufruf in Zeile 50 aktiviert den Render-Thread.
78 void ExampleGLWidget::mouseDoubleClickEvent(QMouseEvent *event)
79 {
80 // get the name of the clicked surface
81 int face = glt.faceAtPosition(event->pos());
82 if (face != -1)
83 {
84 QColor color = QColorDialog::getColor(glt.faceColors[face],
85 this);
86 if (color.isValid())
87 {
88 glt.faceColors[face] = color;
89 }
90 }
91 }
Diese Funktion demonstriert das Picking. Der Aufruf in Zeile 81
holt die ID-Nummer der angeklickten Würfel-Fläche.
faceAtPosition() ist eine Methode der Rendering-Thread-Klasse, in
der das GL-Picking implementiert ist (siehe unten). Zu beachten ist hier,
dass dieser Methoden-Aufruf aus dem Haupt-GUI-Thread heraus erfolgt. Aus
diesem Grund müssen alle Aufrufe in der faceAtPosition() besonders
gesichert werden.
124 void ExampleGLWidget::resizeEvent( QResizeEvent * _e )
125 {
126 // signal the rendering thread that a resize is needed
127 glt.resizeViewport(_e->size());
128
129 render();
130 }
Ist eine Größenänderung des OpenGL-Viewports nötig, zum Beispiel weil die
Fenstergröße geändert wurde, so wird die neue Größe dem Rendering-Thread
mitgeteilt: resizeViewport(). Danach weckt ein render()
den Thread auf und die Änderung wird umgesetzt.
132 void ExampleGLWidget::lockGLContext( )
133 {
134 // lock the render mutex for the calling thread
135 renderMutex().lock();
136 // make the render context current for the calling thread
137 makeCurrent();
138 }
139
140 void ExampleGLWidget::unlockGLContext( )
141 {
142 // release the render context for the calling thread
143 // to make it available for other threads
144 doneCurrent();
145 // unlock the render mutex for the calling thread
146 renderMutex().unlock();
147 }
148
Die beiden obigen Methoden dienen dazu, den Rendering-Kontext des
QGLWidgets aus mehren Threads heraus benutzen zu können. In diesem
Beispiel ist dies wichtig, da das Picking aus dem Haupt-GUI-Thread
heraus aufgerufen wird. lockGLContext() sichert über einen
QMutex den Zugriff auf den Rendering-Context für den aufrufenden
Thread und aktiviert ihn: makeCurrent(). Analog dazu macht
unlockGLContext() dies wieder rückgängig und gibt den
Rendering-Context frei.
149 void ExampleGLWidget::render( )
150 {
151 // let the wait condition wake up the waiting thread
152 renderCondition().wakeAll();
153 }
Die render() Methode tut nicht mehr, als mit Hilfe einer
QWaitCondition den wartenden Rendering-Thread aufzuwecken, damit
dieser einen neuen Frame erzeugen kann.
examplerenderthread.cpp
73 void ExampleRenderThread::run( )
74 {
75 // lock the render mutex of the Gl widget
76 // and makes the rendering context of the glwidget current in this thread
77 glw.lockGLContext();
78
Den GL-Rendering-Kontext für das Rendern aus diesem Thread sichern.
79 // general GL init
80 initializeGL();
81
82 // do as long as the flag is true
83 while( render_flag )
84 {
85 // resize the GL viewport if requested
86 if (resize_flag)
87 {
88 resizeGL(viewport_size.width(), viewport_size.height());
89 resize_flag = false;
90 }
91
92 // render code goes here
93 paintGL();
94
95 // swap the buffers of the GL widget
96 glw.swapBuffers();
97
Da das automatische Wechseln des Buffers im QGLWidget
deaktiviert wurde, muss es hier manuell nach dem Zeichnen geschehen.
98 glw.doneCurrent(); // release the GL render context to make picking work!
99
100 // wait until the gl widget says that there is something to render
101 // glwidget.lockGlContext() had to be called before (see top of the function)!
102 // this will release the render mutex until the wait condition is met
103 // and will lock the render mutex again before exiting
104 // waiting this way instead of insane looping will not waste any CPU ressources
105 glw.renderCondition().wait(&glw.renderMutex());
106
107 glw.makeCurrent(); // get the GL render context back
Der Thread gibt nach jedem Frame den Rendering-Kontext frei und legt sich
schlafen. Wenn er über die QWaitCondition wieder geweckt wird,
wird der Rendering-Kontext erneut für diesen Thread gesichert.
112 }
113 // unlock the render mutex before exit
114 glw.unlockGLContext();
115 }
116
Am Ende muss der Rendering-Kontext wieder freigegeben werden.
146 void ExampleRenderThread::draw()
147 {
...
177 for (int i = 0; i < 6; ++i)
178 {
179 // assign names for each surface
180 // this make picking work
181 glLoadName(i);
182 glBegin(GL_QUADS);
183 glw.qglColor(faceColors[i]);
184 for (int j = 0; j < 4; ++j)
185 {
186 glVertex3f(coords[i][j][0], coords[i][j][1],
187 coords[i][j][2]);
188 }
189 glEnd();
190 }
191 }
192
Zeile 181 ist der Schlüssel für das Picking: jeder Würfelfläche wird
hier ein “Name” zugewiesen, über den diese identifiziert werden kann.
Leider gibt es davon nur 64. Aber das ist ein anderes Thema.
193 int ExampleRenderThread::faceAtPosition(const QPoint &pos)
194 {
195 // we need to lock the rendering context
196 glw.lockGLContext();
197
198 // this is the same as in every OpenGL picking example
199 const int MaxSize = 512; // see below for an explanation on the buffer content
200 GLuint buffer[MaxSize];
201 GLint viewport[4];
202
203 glGetIntegerv(GL_VIEWPORT, viewport);
204 glSelectBuffer(MaxSize, buffer);
205 // enter select mode
206 glRenderMode(GL_SELECT);
207
208 glInitNames();
209 glPushName(0);
210
211 glMatrixMode(GL_PROJECTION);
212 glPushMatrix();
213 glLoadIdentity();
214 gluPickMatrix((GLdouble)pos.x(),
215 (GLdouble)(viewport[3] - pos.y()),
216 5.0, 5.0, viewport);
217 GLfloat x = (GLfloat)viewport_size.width() / viewport_size.height();
218 glFrustum(-x, x, -1.0, 1.0, 4.0, 15.0);
219 draw();
220 glMatrixMode(GL_PROJECTION);
221 glPopMatrix();
222
223
224 // finally release the rendering context again
225 if (!glRenderMode(GL_RENDER))
226 {
227 glw.unlockGLContext();
228 return -1;
229 }
230 glw.unlockGLContext();
231
232 // Each hit takes 4 items in the buffer.
233 // The first item is the number of names on the name stack when the hit occured.
234 // The second item is the minimum z value of all the verticies that intersected
235 // the viewing area at the time of the hit. The third item is the maximum z value
236 // of all the vertices that intersected the viewing area at the time of the hit
237 // and the last item is the content of the name stack at the time of the hit
238 // (name of the object). We are only interested in the object name
239 // (number of the surface).
240
241 // return the name of the clicked surface
242 return buffer[3];
243 }
Hier nun die Implementation des GL-Picking. Damit diese Methode
gefahrlos aus einem anderen Thread aufgerufen werden kann, muss sie sich
erst den Rendering-Kontext sichern. Wurde eine Fläche des Würfels
angeklickt so findet sich der “Name” der Fläche (siehe Zeile 181 oben) im
Hit-Buffer an der vierten Stelle.