1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sean Harmer <sean.harmer@kdab.com>
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qopenglvertexarrayobject.h"
41 
42 #include <QtCore/private/qobject_p.h>
43 #include <QtCore/qthread.h>
44 #include <QtGui/qopenglcontext.h>
45 #include <QtGui/qoffscreensurface.h>
46 #include <QtGui/qguiapplication.h>
47 
48 #include <QtGui/qopenglfunctions_3_0.h>
49 #include <QtGui/qopenglfunctions_3_2_core.h>
50 
51 #include <private/qopenglextensions_p.h>
52 #include <private/qopenglvertexarrayobject_p.h>
53 
54 QT_BEGIN_NAMESPACE
55 
56 class QOpenGLFunctions_3_0;
57 class QOpenGLFunctions_3_2_Core;
58 
qtInitializeVertexArrayObjectHelper(QOpenGLVertexArrayObjectHelper * helper,QOpenGLContext * context)59 void qtInitializeVertexArrayObjectHelper(QOpenGLVertexArrayObjectHelper *helper, QOpenGLContext *context)
60 {
61     Q_ASSERT(helper);
62     Q_ASSERT(context);
63 
64     bool tryARB = true;
65 
66     if (context->isOpenGLES()) {
67         if (context->format().majorVersion() >= 3) {
68             QOpenGLExtraFunctionsPrivate *extra = static_cast<QOpenGLExtensions *>(context->extraFunctions())->d();
69             helper->GenVertexArrays = extra->f.GenVertexArrays;
70             helper->DeleteVertexArrays = extra->f.DeleteVertexArrays;
71             helper->BindVertexArray = extra->f.BindVertexArray;
72             helper->IsVertexArray = extra->f.IsVertexArray;
73             tryARB = false;
74         } else if (context->hasExtension(QByteArrayLiteral("GL_OES_vertex_array_object"))) {
75             helper->GenVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_GenVertexArrays_t>(context->getProcAddress("glGenVertexArraysOES"));
76             helper->DeleteVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_DeleteVertexArrays_t>(context->getProcAddress("glDeleteVertexArraysOES"));
77             helper->BindVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_BindVertexArray_t>(context->getProcAddress("glBindVertexArrayOES"));
78             helper->IsVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_IsVertexArray_t>(context->getProcAddress("glIsVertexArrayOES"));
79             tryARB = false;
80         }
81     } else if (context->hasExtension(QByteArrayLiteral("GL_APPLE_vertex_array_object")) &&
82                !context->hasExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) {
83         helper->GenVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_GenVertexArrays_t>(context->getProcAddress("glGenVertexArraysAPPLE"));
84         helper->DeleteVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_DeleteVertexArrays_t>(context->getProcAddress("glDeleteVertexArraysAPPLE"));
85         helper->BindVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_BindVertexArray_t>(context->getProcAddress("glBindVertexArrayAPPLE"));
86         helper->IsVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_IsVertexArray_t>(context->getProcAddress("glIsVertexArrayAPPLE"));
87         tryARB = false;
88     }
89 
90     if (tryARB && context->hasExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) {
91         helper->GenVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_GenVertexArrays_t>(context->getProcAddress("glGenVertexArrays"));
92         helper->DeleteVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_DeleteVertexArrays_t>(context->getProcAddress("glDeleteVertexArrays"));
93         helper->BindVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_BindVertexArray_t>(context->getProcAddress("glBindVertexArray"));
94         helper->IsVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_IsVertexArray_t>(context->getProcAddress("glIsVertexArray"));
95     }
96 }
97 
98 class QOpenGLVertexArrayObjectPrivate : public QObjectPrivate
99 {
100 public:
QOpenGLVertexArrayObjectPrivate()101     QOpenGLVertexArrayObjectPrivate()
102         : vao(0)
103         , vaoFuncsType(NotSupported)
104         , context(nullptr)
105         , guiThread(nullptr)
106     {
107     }
108 
~QOpenGLVertexArrayObjectPrivate()109     ~QOpenGLVertexArrayObjectPrivate()
110     {
111         if (vaoFuncsType == ARB || vaoFuncsType == APPLE || vaoFuncsType == OES)
112             delete vaoFuncs.helper;
113     }
114 
115     bool create();
116     void destroy();
117     void bind();
118     void release();
119     void _q_contextAboutToBeDestroyed();
120 
121     Q_DECLARE_PUBLIC(QOpenGLVertexArrayObject)
122 
123     GLuint vao;
124 
125     union {
126         QOpenGLFunctions_3_0 *core_3_0;
127         QOpenGLFunctions_3_2_Core *core_3_2;
128         QOpenGLVertexArrayObjectHelper *helper;
129     } vaoFuncs;
130     enum {
131         NotSupported,
132         Core_3_0,
133         Core_3_2,
134         ARB,
135         APPLE,
136         OES
137     } vaoFuncsType;
138 
139     QOpenGLContext *context;
140     QThread *guiThread;
141 };
142 
create()143 bool QOpenGLVertexArrayObjectPrivate::create()
144 {
145     if (vao) {
146         qWarning("QOpenGLVertexArrayObject::create() VAO is already created");
147         return false;
148     }
149 
150     Q_Q(QOpenGLVertexArrayObject);
151 
152     QOpenGLContext *ctx = QOpenGLContext::currentContext();
153     if (!ctx) {
154         qWarning("QOpenGLVertexArrayObject::create() requires a valid current OpenGL context");
155         return false;
156     }
157 
158     //Fail early, if context is the same as ctx, it means we have tried to initialize for this context and failed
159     if (ctx == context)
160         return false;
161 
162     context = ctx;
163     QObject::connect(context, SIGNAL(aboutToBeDestroyed()), q, SLOT(_q_contextAboutToBeDestroyed()));
164 
165     guiThread = qGuiApp->thread();
166 
167     if (ctx->isOpenGLES()) {
168         if (ctx->format().majorVersion() >= 3 || ctx->hasExtension(QByteArrayLiteral("GL_OES_vertex_array_object"))) {
169             vaoFuncs.helper = new QOpenGLVertexArrayObjectHelper(ctx);
170             vaoFuncsType = OES;
171             vaoFuncs.helper->glGenVertexArrays(1, &vao);
172         }
173     } else {
174         vaoFuncs.core_3_0 = nullptr;
175         vaoFuncsType = NotSupported;
176         QSurfaceFormat format = ctx->format();
177 #ifndef QT_OPENGL_ES_2
178         if (format.version() >= qMakePair<int, int>(3,2)) {
179             vaoFuncs.core_3_2 = ctx->versionFunctions<QOpenGLFunctions_3_2_Core>();
180             vaoFuncsType = Core_3_2;
181             vaoFuncs.core_3_2->glGenVertexArrays(1, &vao);
182         } else if (format.majorVersion() >= 3) {
183             vaoFuncs.core_3_0 = ctx->versionFunctions<QOpenGLFunctions_3_0>();
184             vaoFuncsType = Core_3_0;
185             vaoFuncs.core_3_0->glGenVertexArrays(1, &vao);
186         } else
187 #endif
188         if (ctx->hasExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) {
189             vaoFuncs.helper = new QOpenGLVertexArrayObjectHelper(ctx);
190             vaoFuncsType = ARB;
191             vaoFuncs.helper->glGenVertexArrays(1, &vao);
192         } else if (ctx->hasExtension(QByteArrayLiteral("GL_APPLE_vertex_array_object"))) {
193             vaoFuncs.helper = new QOpenGLVertexArrayObjectHelper(ctx);
194             vaoFuncsType = APPLE;
195             vaoFuncs.helper->glGenVertexArrays(1, &vao);
196         }
197     }
198 
199     return (vao != 0);
200 }
201 
destroy()202 void QOpenGLVertexArrayObjectPrivate::destroy()
203 {
204     Q_Q(QOpenGLVertexArrayObject);
205 
206     QOpenGLContext *ctx = QOpenGLContext::currentContext();
207     QOpenGLContext *oldContext = nullptr;
208     QSurface *oldContextSurface = nullptr;
209     QScopedPointer<QOffscreenSurface> offscreenSurface;
210     if (context && context != ctx) {
211         oldContext = ctx;
212         oldContextSurface = ctx ? ctx->surface() : nullptr;
213         // Before going through the effort of creating an offscreen surface
214         // check that we are on the GUI thread because otherwise many platforms
215         // will not able to create that offscreen surface.
216         if (QThread::currentThread() != guiThread) {
217             ctx = nullptr;
218         } else {
219             // Cannot just make the current surface current again with another context.
220             // The format may be incompatible and some platforms (iOS) may impose
221             // restrictions on using a window with different contexts. Create an
222             // offscreen surface (a pbuffer or a hidden window) instead to be safe.
223             offscreenSurface.reset(new QOffscreenSurface);
224             offscreenSurface->setFormat(context->format());
225             offscreenSurface->create();
226             if (context->makeCurrent(offscreenSurface.data())) {
227                 ctx = context;
228             } else {
229                 qWarning("QOpenGLVertexArrayObject::destroy() failed to make VAO's context current");
230                 ctx = nullptr;
231             }
232         }
233     }
234 
235     if (context) {
236         QObject::disconnect(context, SIGNAL(aboutToBeDestroyed()), q, SLOT(_q_contextAboutToBeDestroyed()));
237         context = nullptr;
238     }
239 
240     if (vao && ctx) {
241         switch (vaoFuncsType) {
242 #ifndef QT_OPENGL_ES_2
243         case Core_3_2:
244             vaoFuncs.core_3_2->glDeleteVertexArrays(1, &vao);
245             break;
246         case Core_3_0:
247             vaoFuncs.core_3_0->glDeleteVertexArrays(1, &vao);
248             break;
249 #endif
250         case ARB:
251         case APPLE:
252         case OES:
253             vaoFuncs.helper->glDeleteVertexArrays(1, &vao);
254             break;
255         default:
256             break;
257         }
258 
259         vao = 0;
260     }
261 
262     if (oldContext && oldContextSurface) {
263         if (!oldContext->makeCurrent(oldContextSurface))
264             qWarning("QOpenGLVertexArrayObject::destroy() failed to restore current context");
265     }
266 }
267 
268 /*!
269     \internal
270 */
_q_contextAboutToBeDestroyed()271 void QOpenGLVertexArrayObjectPrivate::_q_contextAboutToBeDestroyed()
272 {
273     destroy();
274 }
275 
bind()276 void QOpenGLVertexArrayObjectPrivate::bind()
277 {
278     switch (vaoFuncsType) {
279 #ifndef QT_OPENGL_ES_2
280     case Core_3_2:
281         vaoFuncs.core_3_2->glBindVertexArray(vao);
282         break;
283     case Core_3_0:
284         vaoFuncs.core_3_0->glBindVertexArray(vao);
285         break;
286 #endif
287     case ARB:
288     case APPLE:
289     case OES:
290         vaoFuncs.helper->glBindVertexArray(vao);
291         break;
292     default:
293         break;
294     }
295 }
296 
release()297 void QOpenGLVertexArrayObjectPrivate::release()
298 {
299     switch (vaoFuncsType) {
300 #ifndef QT_OPENGL_ES_2
301     case Core_3_2:
302         vaoFuncs.core_3_2->glBindVertexArray(0);
303         break;
304     case Core_3_0:
305         vaoFuncs.core_3_0->glBindVertexArray(0);
306         break;
307 #endif
308     case ARB:
309     case APPLE:
310     case OES:
311         vaoFuncs.helper->glBindVertexArray(0);
312         break;
313     default:
314         break;
315     }
316 }
317 
318 
319 /*!
320     \class QOpenGLVertexArrayObject
321     \brief The QOpenGLVertexArrayObject class wraps an OpenGL Vertex Array Object.
322     \inmodule QtGui
323     \since 5.1
324     \ingroup painting-3D
325 
326     A Vertex Array Object (VAO) is an OpenGL container object that encapsulates
327     the state needed to specify per-vertex attribute data to the OpenGL pipeline.
328     To put it another way, a VAO remembers the states of buffer objects (see
329     QOpenGLBuffer) and their associated state (e.g. vertex attribute divisors).
330     This allows a very easy and efficient method of switching between OpenGL buffer
331     states for rendering different "objects" in a scene. The QOpenGLVertexArrayObject
332     class is a thin wrapper around an OpenGL VAO.
333 
334     For the desktop, VAOs are supported as a core feature in OpenGL 3.0 or newer and by the
335     GL_ARB_vertex_array_object for older versions. On OpenGL ES 2, VAOs are provided by
336     the optional GL_OES_vertex_array_object extension. You can check the version of
337     OpenGL with QOpenGLContext::surfaceFormat() and check for the presence of extensions
338     with QOpenGLContext::hasExtension().
339 
340     As with the other Qt OpenGL classes, QOpenGLVertexArrayObject has a create()
341     function to create the underlying OpenGL object. This is to allow the developer to
342     ensure that there is a valid current OpenGL context at the time.
343 
344     Once you have successfully created a VAO the typical usage pattern is:
345 
346     \list
347         \li In scene initialization function, for each visual object:
348         \list
349             \li Bind the VAO
350             \li Set vertex data state for this visual object (vertices, normals, texture coordinates etc.)
351             \li Unbind (release()) the VAO
352         \endlist
353         \li In render function, for each visual object:
354         \list
355             \li Bind the VAO (and shader program if needed)
356             \li Call a glDraw*() function
357             \li Unbind (release()) the VAO
358         \endlist
359     \endlist
360 
361     The act of binding the VAO in the render function has the effect of restoring
362     all of the vertex data state setup in the initialization phase. In this way we can
363     set a great deal of state when setting up a VAO and efficiently switch between
364     state sets of objects to be rendered. Using VAOs also allows the OpenGL driver
365     to amortise the validation checks of the vertex data.
366 
367     \note Vertex Array Objects, like all other OpenGL container objects, are specific
368     to the context for which they were created and cannot be shared amongst a
369     context group.
370 
371     \sa QOpenGLVertexArrayObject::Binder, QOpenGLBuffer
372 */
373 
374 /*!
375     Creates a QOpenGLVertexArrayObject with the given \a parent. You must call create()
376     with a valid OpenGL context before using.
377 */
QOpenGLVertexArrayObject(QObject * parent)378 QOpenGLVertexArrayObject::QOpenGLVertexArrayObject(QObject* parent)
379     : QObject(*new QOpenGLVertexArrayObjectPrivate, parent)
380 {
381 }
382 
383 /*!
384     \internal
385 */
QOpenGLVertexArrayObject(QOpenGLVertexArrayObjectPrivate & dd)386 QOpenGLVertexArrayObject::QOpenGLVertexArrayObject(QOpenGLVertexArrayObjectPrivate &dd)
387     : QObject(dd)
388 {
389 }
390 
391 /*!
392     Destroys the QOpenGLVertexArrayObject and the underlying OpenGL resource.
393 */
~QOpenGLVertexArrayObject()394 QOpenGLVertexArrayObject::~QOpenGLVertexArrayObject()
395 {
396     destroy();
397 }
398 
399 /*!
400     Creates the underlying OpenGL vertex array object. There must be a valid OpenGL context
401     that supports vertex array objects current for this function to succeed.
402 
403     Returns \c true if the OpenGL vertex array object was successfully created.
404 
405     When the return value is \c false, vertex array object support is not available. This
406     is not an error: on systems with OpenGL 2.x or OpenGL ES 2.0 vertex array objects may
407     not be supported. The application is free to continue execution in this case, but it
408     then has to be prepared to operate in a VAO-less manner too. This means that instead
409     of merely calling bind(), the value of isCreated() must be checked and the vertex
410     arrays has to be initialized in the traditional way when there is no vertex array
411     object present.
412 
413     \sa isCreated()
414 */
create()415 bool QOpenGLVertexArrayObject::create()
416 {
417     Q_D(QOpenGLVertexArrayObject);
418     return d->create();
419 }
420 
421 /*!
422     Destroys the underlying OpenGL vertex array object. There must be a valid OpenGL context
423     that supports vertex array objects current for this function to succeed.
424 */
destroy()425 void QOpenGLVertexArrayObject::destroy()
426 {
427     Q_D(QOpenGLVertexArrayObject);
428     d->destroy();
429 }
430 
431 /*!
432     Returns \c true is the underlying OpenGL vertex array object has been created. If this
433     returns \c true and the associated OpenGL context is current, then you are able to bind()
434     this object.
435 */
isCreated() const436 bool QOpenGLVertexArrayObject::isCreated() const
437 {
438     Q_D(const QOpenGLVertexArrayObject);
439     return (d->vao != 0);
440 }
441 
442 /*!
443     Returns the id of the underlying OpenGL vertex array object.
444 */
objectId() const445 GLuint QOpenGLVertexArrayObject::objectId() const
446 {
447     Q_D(const QOpenGLVertexArrayObject);
448     return d->vao;
449 }
450 
451 /*!
452     Binds this vertex array object to the OpenGL binding point. From this point on
453     and until release() is called or another vertex array object is bound, any
454     modifications made to vertex data state are stored inside this vertex array object.
455 
456     If another vertex array object is then bound you can later restore the set of
457     state associated with this object by calling bind() on this object once again.
458     This allows efficient changes between vertex data states in rendering functions.
459 */
bind()460 void QOpenGLVertexArrayObject::bind()
461 {
462     Q_D(QOpenGLVertexArrayObject);
463     d->bind();
464 }
465 
466 /*!
467     Unbinds this vertex array object by binding the default vertex array object (id = 0).
468 */
release()469 void QOpenGLVertexArrayObject::release()
470 {
471     Q_D(QOpenGLVertexArrayObject);
472     d->release();
473 }
474 
475 
476 /*!
477     \class QOpenGLVertexArrayObject::Binder
478     \brief The QOpenGLVertexArrayObject::Binder class is a convenience class to help
479     with the binding and releasing of OpenGL Vertex Array Objects.
480     \inmodule QtGui
481     \reentrant
482     \since 5.1
483     \ingroup painting-3D
484 
485     QOpenGLVertexArrayObject::Binder is a simple convenience class that can be used
486     to assist with the binding and releasing of QOpenGLVertexArrayObject instances.
487     This class is to QOpenGLVertexArrayObject as QMutexLocker is to QMutex.
488 
489     This class implements the RAII principle which helps to ensure behavior in
490     complex code or in the presence of exceptions.
491 
492     The constructor of this class accepts a QOpenGLVertexArrayObject (VAO) as an
493     argument and attempts to bind the VAO, calling QOpenGLVertexArrayObject::create()
494     if necessary. The destructor of this class calls QOpenGLVertexArrayObject::release()
495     which unbinds the VAO.
496 
497     If needed the VAO can be temporarily unbound with the release() function and bound
498     once more with rebind().
499 
500     \sa QOpenGLVertexArrayObject
501 */
502 
503 /*!
504     \fn QOpenGLVertexArrayObject::Binder::Binder(QOpenGLVertexArrayObject *v)
505 
506     Creates a QOpenGLVertexArrayObject::Binder object and binds \a v by calling
507     QOpenGLVertexArrayObject::bind(). If necessary it first calls
508     QOpenGLVertexArrayObject::create().
509 */
510 
511 /*!
512     \fn QOpenGLVertexArrayObject::Binder::~Binder()
513 
514     Destroys the QOpenGLVertexArrayObject::Binder and releases the associated vertex array object.
515 */
516 
517 /*!
518     \fn QOpenGLVertexArrayObject::Binder::release()
519 
520     Can be used to temporarily release the associated vertex array object.
521 
522     \sa rebind()
523 */
524 
525 /*!
526     \fn QOpenGLVertexArrayObject::Binder::rebind()
527 
528     Can be used to rebind the associated vertex array object.
529 
530     \sa release()
531 */
532 
533 QT_END_NAMESPACE
534 
535 #include "moc_qopenglvertexarrayobject.cpp"
536