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
54QT_BEGIN_NAMESPACE
55
56class QOpenGLFunctions_3_0;
57class QOpenGLFunctions_3_2_Core;
58
59void 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
98class QOpenGLVertexArrayObjectPrivate : public QObjectPrivate
99{
100public:
101 QOpenGLVertexArrayObjectPrivate()
102 : vao(0)
103 , vaoFuncsType(NotSupported)
104 , context(0)
105 {
106 }
107
108 ~QOpenGLVertexArrayObjectPrivate()
109 {
110 if (vaoFuncsType == ARB || vaoFuncsType == APPLE || vaoFuncsType == OES)
111 delete vaoFuncs.helper;
112 }
113
114 bool create();
115 void destroy();
116 void bind();
117 void release();
118 void _q_contextAboutToBeDestroyed();
119
120 Q_DECLARE_PUBLIC(QOpenGLVertexArrayObject)
121
122 GLuint vao;
123
124 union {
125 QOpenGLFunctions_3_0 *core_3_0;
126 QOpenGLFunctions_3_2_Core *core_3_2;
127 QOpenGLVertexArrayObjectHelper *helper;
128 } vaoFuncs;
129 enum {
130 NotSupported,
131 Core_3_0,
132 Core_3_2,
133 ARB,
134 APPLE,
135 OES
136 } vaoFuncsType;
137
138 QOpenGLContext *context;
139};
140
141bool QOpenGLVertexArrayObjectPrivate::create()
142{
143 if (vao) {
144 qWarning("QOpenGLVertexArrayObject::create() VAO is already created");
145 return false;
146 }
147
148 Q_Q(QOpenGLVertexArrayObject);
149
150 QOpenGLContext *ctx = QOpenGLContext::currentContext();
151 if (!ctx) {
152 qWarning("QOpenGLVertexArrayObject::create() requires a valid current OpenGL context");
153 return false;
154 }
155
156 //Fail early, if context is the same as ctx, it means we have tried to initialize for this context and failed
157 if (ctx == context)
158 return false;
159
160 context = ctx;
161 QObject::connect(context, SIGNAL(aboutToBeDestroyed()), q, SLOT(_q_contextAboutToBeDestroyed()));
162
163 if (ctx->isOpenGLES()) {
164 if (ctx->format().majorVersion() >= 3 || ctx->hasExtension(QByteArrayLiteral("GL_OES_vertex_array_object"))) {
165 vaoFuncs.helper = new QOpenGLVertexArrayObjectHelper(ctx);
166 vaoFuncsType = OES;
167 vaoFuncs.helper->glGenVertexArrays(1, &vao);
168 }
169 } else {
170 vaoFuncs.core_3_0 = 0;
171 vaoFuncsType = NotSupported;
172 QSurfaceFormat format = ctx->format();
173#ifndef QT_OPENGL_ES_2
174 if (format.version() >= qMakePair<int, int>(3,2)) {
175 vaoFuncs.core_3_2 = ctx->versionFunctions<QOpenGLFunctions_3_2_Core>();
176 vaoFuncsType = Core_3_2;
177 vaoFuncs.core_3_2->glGenVertexArrays(1, &vao);
178 } else if (format.majorVersion() >= 3) {
179 vaoFuncs.core_3_0 = ctx->versionFunctions<QOpenGLFunctions_3_0>();
180 vaoFuncsType = Core_3_0;
181 vaoFuncs.core_3_0->glGenVertexArrays(1, &vao);
182 } else
183#endif
184 if (ctx->hasExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) {
185 vaoFuncs.helper = new QOpenGLVertexArrayObjectHelper(ctx);
186 vaoFuncsType = ARB;
187 vaoFuncs.helper->glGenVertexArrays(1, &vao);
188 } else if (ctx->hasExtension(QByteArrayLiteral("GL_APPLE_vertex_array_object"))) {
189 vaoFuncs.helper = new QOpenGLVertexArrayObjectHelper(ctx);
190 vaoFuncsType = APPLE;
191 vaoFuncs.helper->glGenVertexArrays(1, &vao);
192 }
193 }
194
195 return (vao != 0);
196}
197
198void QOpenGLVertexArrayObjectPrivate::destroy()
199{
200 Q_Q(QOpenGLVertexArrayObject);
201
202 QOpenGLContext *ctx = QOpenGLContext::currentContext();
203 QOpenGLContext *oldContext = 0;
204 QSurface *oldContextSurface = 0;
205 QScopedPointer<QOffscreenSurface> offscreenSurface;
206 if (context && context != ctx) {
207 oldContext = ctx;
208 oldContextSurface = ctx ? ctx->surface() : 0;
209 // Before going through the effort of creating an offscreen surface
210 // check that we are on the GUI thread because otherwise many platforms
211 // will not able to create that offscreen surface.
212 if (QThread::currentThread() != qGuiApp->thread()) {
213 ctx = 0;
214 } else {
215 // Cannot just make the current surface current again with another context.
216 // The format may be incompatible and some platforms (iOS) may impose
217 // restrictions on using a window with different contexts. Create an
218 // offscreen surface (a pbuffer or a hidden window) instead to be safe.
219 offscreenSurface.reset(new QOffscreenSurface);
220 offscreenSurface->setFormat(context->format());
221 offscreenSurface->create();
222 if (context->makeCurrent(offscreenSurface.data())) {
223 ctx = context;
224 } else {
225 qWarning("QOpenGLVertexArrayObject::destroy() failed to make VAO's context current");
226 ctx = 0;
227 }
228 }
229 }
230
231 if (context) {
232 QObject::disconnect(context, SIGNAL(aboutToBeDestroyed()), q, SLOT(_q_contextAboutToBeDestroyed()));
233 context = 0;
234 }
235
236 if (vao && ctx) {
237 switch (vaoFuncsType) {
238#ifndef QT_OPENGL_ES_2
239 case Core_3_2:
240 vaoFuncs.core_3_2->glDeleteVertexArrays(1, &vao);
241 break;
242 case Core_3_0:
243 vaoFuncs.core_3_0->glDeleteVertexArrays(1, &vao);
244 break;
245#endif
246 case ARB:
247 case APPLE:
248 case OES:
249 vaoFuncs.helper->glDeleteVertexArrays(1, &vao);
250 break;
251 default:
252 break;
253 }
254
255 vao = 0;
256 }
257
258 if (oldContext && oldContextSurface) {
259 if (!oldContext->makeCurrent(oldContextSurface))
260 qWarning("QOpenGLVertexArrayObject::destroy() failed to restore current context");
261 }
262}
263
264/*!
265 \internal
266*/
267void QOpenGLVertexArrayObjectPrivate::_q_contextAboutToBeDestroyed()
268{
269 destroy();
270}
271
272void QOpenGLVertexArrayObjectPrivate::bind()
273{
274 switch (vaoFuncsType) {
275#ifndef QT_OPENGL_ES_2
276 case Core_3_2:
277 vaoFuncs.core_3_2->glBindVertexArray(vao);
278 break;
279 case Core_3_0:
280 vaoFuncs.core_3_0->glBindVertexArray(vao);
281 break;
282#endif
283 case ARB:
284 case APPLE:
285 case OES:
286 vaoFuncs.helper->glBindVertexArray(vao);
287 break;
288 default:
289 break;
290 }
291}
292
293void QOpenGLVertexArrayObjectPrivate::release()
294{
295 switch (vaoFuncsType) {
296#ifndef QT_OPENGL_ES_2
297 case Core_3_2:
298 vaoFuncs.core_3_2->glBindVertexArray(0);
299 break;
300 case Core_3_0:
301 vaoFuncs.core_3_0->glBindVertexArray(0);
302 break;
303#endif
304 case ARB:
305 case APPLE:
306 case OES:
307 vaoFuncs.helper->glBindVertexArray(0);
308 break;
309 default:
310 break;
311 }
312}
313
314
315/*!
316 \class QOpenGLVertexArrayObject
317 \brief The QOpenGLVertexArrayObject class wraps an OpenGL Vertex Array Object.
318 \inmodule QtGui
319 \since 5.1
320 \ingroup painting-3D
321
322 A Vertex Array Object (VAO) is an OpenGL container object that encapsulates
323 the state needed to specify per-vertex attribute data to the OpenGL pipeline.
324 To put it another way, a VAO remembers the states of buffer objects (see
325 QOpenGLBuffer) and their associated state (e.g. vertex attribute divisors).
326 This allows a very easy and efficient method of switching between OpenGL buffer
327 states for rendering different "objects" in a scene. The QOpenGLVertexArrayObject
328 class is a thin wrapper around an OpenGL VAO.
329
330 For the desktop, VAOs are supported as a core feature in OpenGL 3.0 or newer and by the
331 GL_ARB_vertex_array_object for older versions. On OpenGL ES 2, VAOs are provided by
332 the optional GL_OES_vertex_array_object extension. You can check the version of
333 OpenGL with QOpenGLContext::surfaceFormat() and check for the presence of extensions
334 with QOpenGLContext::hasExtension().
335
336 As with the other Qt OpenGL classes, QOpenGLVertexArrayObject has a create()
337 function to create the underlying OpenGL object. This is to allow the developer to
338 ensure that there is a valid current OpenGL context at the time.
339
340 Once you have successfully created a VAO the typical usage pattern is:
341
342 \list
343 \li In scene initialization function, for each visual object:
344 \list
345 \li Bind the VAO
346 \li Set vertex data state for this visual object (vertices, normals, texture coordinates etc.)
347 \li Unbind (release()) the VAO
348 \endlist
349 \li In render function, for each visual object:
350 \list
351 \li Bind the VAO (and shader program if needed)
352 \li Call a glDraw*() function
353 \li Unbind (release()) the VAO
354 \endlist
355 \endlist
356
357 The act of binding the VAO in the render function has the effect of restoring
358 all of the vertex data state setup in the initialization phase. In this way we can
359 set a great deal of state when setting up a VAO and efficiently switch between
360 state sets of objects to be rendered. Using VAOs also allows the OpenGL driver
361 to amortise the validation checks of the vertex data.
362
363 \note Vertex Array Objects, like all other OpenGL container objects, are specific
364 to the context for which they were created and cannot be shared amongst a
365 context group.
366
367 \sa QOpenGLVertexArrayObject::Binder, QOpenGLBuffer
368*/
369
370/*!
371 Creates a QOpenGLVertexArrayObject with the given \a parent. You must call create()
372 with a valid OpenGL context before using.
373*/
374QOpenGLVertexArrayObject::QOpenGLVertexArrayObject(QObject* parent)
375 : QObject(*new QOpenGLVertexArrayObjectPrivate, parent)
376{
377}
378
379/*!
380 \internal
381*/
382QOpenGLVertexArrayObject::QOpenGLVertexArrayObject(QOpenGLVertexArrayObjectPrivate &dd)
383 : QObject(dd)
384{
385}
386
387/*!
388 Destroys the QOpenGLVertexArrayObject and the underlying OpenGL resource.
389*/
390QOpenGLVertexArrayObject::~QOpenGLVertexArrayObject()
391{
392 destroy();
393}
394
395/*!
396 Creates the underlying OpenGL vertex array object. There must be a valid OpenGL context
397 that supports vertex array objects current for this function to succeed.
398
399 Returns \c true if the OpenGL vertex array object was successfully created.
400
401 When the return value is \c false, vertex array object support is not available. This
402 is not an error: on systems with OpenGL 2.x or OpenGL ES 2.0 vertex array objects may
403 not be supported. The application is free to continue execution in this case, but it
404 then has to be prepared to operate in a VAO-less manner too. This means that instead
405 of merely calling bind(), the value of isCreated() must be checked and the vertex
406 arrays has to be initialized in the traditional way when there is no vertex array
407 object present.
408
409 \sa isCreated()
410*/
411bool QOpenGLVertexArrayObject::create()
412{
413 Q_D(QOpenGLVertexArrayObject);
414 return d->create();
415}
416
417/*!
418 Destroys the underlying OpenGL vertex array object. There must be a valid OpenGL context
419 that supports vertex array objects current for this function to succeed.
420*/
421void QOpenGLVertexArrayObject::destroy()
422{
423 Q_D(QOpenGLVertexArrayObject);
424 d->destroy();
425}
426
427/*!
428 Returns \c true is the underlying OpenGL vertex array object has been created. If this
429 returns \c true and the associated OpenGL context is current, then you are able to bind()
430 this object.
431*/
432bool QOpenGLVertexArrayObject::isCreated() const
433{
434 Q_D(const QOpenGLVertexArrayObject);
435 return (d->vao != 0);
436}
437
438/*!
439 Returns the id of the underlying OpenGL vertex array object.
440*/
441GLuint QOpenGLVertexArrayObject::objectId() const
442{
443 Q_D(const QOpenGLVertexArrayObject);
444 return d->vao;
445}
446
447/*!
448 Binds this vertex array object to the OpenGL binding point. From this point on
449 and until release() is called or another vertex array object is bound, any
450 modifications made to vertex data state are stored inside this vertex array object.
451
452 If another vertex array object is then bound you can later restore the set of
453 state associated with this object by calling bind() on this object once again.
454 This allows efficient changes between vertex data states in rendering functions.
455*/
456void QOpenGLVertexArrayObject::bind()
457{
458 Q_D(QOpenGLVertexArrayObject);
459 d->bind();
460}
461
462/*!
463 Unbinds this vertex array object by binding the default vertex array object (id = 0).
464*/
465void QOpenGLVertexArrayObject::release()
466{
467 Q_D(QOpenGLVertexArrayObject);
468 d->release();
469}
470
471
472/*!
473 \class QOpenGLVertexArrayObject::Binder
474 \brief The QOpenGLVertexArrayObject::Binder class is a convenience class to help
475 with the binding and releasing of OpenGL Vertex Array Objects.
476 \inmodule QtGui
477 \reentrant
478 \since 5.1
479 \ingroup painting-3D
480
481 QOpenGLVertexArrayObject::Binder is a simple convenience class that can be used
482 to assist with the binding and releasing of QOpenGLVertexArrayObject instances.
483 This class is to QOpenGLVertexArrayObject as QMutexLocker is to QMutex.
484
485 This class implements the RAII principle which helps to ensure behavior in
486 complex code or in the presence of exceptions.
487
488 The constructor of this class accepts a QOpenGLVertexArrayObject (VAO) as an
489 argument and attempts to bind the VAO, calling QOpenGLVertexArrayObject::create()
490 if necessary. The destructor of this class calls QOpenGLVertexArrayObject::release()
491 which unbinds the VAO.
492
493 If needed the VAO can be temporarily unbound with the release() function and bound
494 once more with rebind().
495
496 \sa QOpenGLVertexArrayObject
497*/
498
499/*!
500 \fn QOpenGLVertexArrayObject::Binder::Binder(QOpenGLVertexArrayObject *v)
501
502 Creates a QOpenGLVertexArrayObject::Binder object and binds \a v by calling
503 QOpenGLVertexArrayObject::bind(). If necessary it first calls
504 QOpenGLVertexArrayObject::create().
505*/
506
507/*!
508 \fn QOpenGLVertexArrayObject::Binder::~Binder()
509
510 Destroys the QOpenGLVertexArrayObject::Binder and releases the associated vertex array object.
511*/
512
513/*!
514 \fn QOpenGLVertexArrayObject::Binder::release()
515
516 Can be used to temporarily release the associated vertex array object.
517
518 \sa rebind()
519*/
520
521/*!
522 \fn QOpenGLVertexArrayObject::Binder::rebind()
523
524 Can be used to rebind the associated vertex array object.
525
526 \sa release()
527*/
528
529QT_END_NAMESPACE
530
531#include "moc_qopenglvertexarrayobject.cpp"
532