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