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(procName: "glGenVertexArraysOES"));
76 helper->DeleteVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_DeleteVertexArrays_t>(context->getProcAddress(procName: "glDeleteVertexArraysOES"));
77 helper->BindVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_BindVertexArray_t>(context->getProcAddress(procName: "glBindVertexArrayOES"));
78 helper->IsVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_IsVertexArray_t>(context->getProcAddress(procName: "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(procName: "glGenVertexArraysAPPLE"));
84 helper->DeleteVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_DeleteVertexArrays_t>(context->getProcAddress(procName: "glDeleteVertexArraysAPPLE"));
85 helper->BindVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_BindVertexArray_t>(context->getProcAddress(procName: "glBindVertexArrayAPPLE"));
86 helper->IsVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_IsVertexArray_t>(context->getProcAddress(procName: "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(procName: "glGenVertexArrays"));
92 helper->DeleteVertexArrays = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_DeleteVertexArrays_t>(context->getProcAddress(procName: "glDeleteVertexArrays"));
93 helper->BindVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_BindVertexArray_t>(context->getProcAddress(procName: "glBindVertexArray"));
94 helper->IsVertexArray = reinterpret_cast<QOpenGLVertexArrayObjectHelper::qt_IsVertexArray_t>(context->getProcAddress(procName: "glIsVertexArray"));
95 }
96}
97
98class QOpenGLVertexArrayObjectPrivate : public QObjectPrivate
99{
100public:
101 QOpenGLVertexArrayObjectPrivate()
102 : vao(0)
103 , vaoFuncsType(NotSupported)
104 , context(nullptr)
105 , guiThread(nullptr)
106 {
107 }
108
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
143bool QOpenGLVertexArrayObjectPrivate::create()
144{
145 if (vao) {
146 qWarning(msg: "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(msg: "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(sender: context, SIGNAL(aboutToBeDestroyed()), receiver: 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(n: 1, arrays: &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>(x: 3,y: 2)) {
179 vaoFuncs.core_3_2 = ctx->versionFunctions<QOpenGLFunctions_3_2_Core>();
180 vaoFuncsType = Core_3_2;
181 vaoFuncs.core_3_2->glGenVertexArrays(n: 1, arrays: &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(n: 1, arrays: &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(n: 1, arrays: &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(n: 1, arrays: &vao);
196 }
197 }
198
199 return (vao != 0);
200}
201
202void 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(other: new QOffscreenSurface);
224 offscreenSurface->setFormat(context->format());
225 offscreenSurface->create();
226 if (context->makeCurrent(surface: offscreenSurface.data())) {
227 ctx = context;
228 } else {
229 qWarning(msg: "QOpenGLVertexArrayObject::destroy() failed to make VAO's context current");
230 ctx = nullptr;
231 }
232 }
233 }
234
235 if (context) {
236 QObject::disconnect(sender: context, SIGNAL(aboutToBeDestroyed()), receiver: 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(n: 1, arrays: &vao);
245 break;
246 case Core_3_0:
247 vaoFuncs.core_3_0->glDeleteVertexArrays(n: 1, arrays: &vao);
248 break;
249#endif
250 case ARB:
251 case APPLE:
252 case OES:
253 vaoFuncs.helper->glDeleteVertexArrays(n: 1, arrays: &vao);
254 break;
255 default:
256 break;
257 }
258
259 vao = 0;
260 }
261
262 if (oldContext && oldContextSurface) {
263 if (!oldContext->makeCurrent(surface: oldContextSurface))
264 qWarning(msg: "QOpenGLVertexArrayObject::destroy() failed to restore current context");
265 }
266}
267
268/*!
269 \internal
270*/
271void QOpenGLVertexArrayObjectPrivate::_q_contextAboutToBeDestroyed()
272{
273 destroy();
274}
275
276void QOpenGLVertexArrayObjectPrivate::bind()
277{
278 switch (vaoFuncsType) {
279#ifndef QT_OPENGL_ES_2
280 case Core_3_2:
281 vaoFuncs.core_3_2->glBindVertexArray(array: vao);
282 break;
283 case Core_3_0:
284 vaoFuncs.core_3_0->glBindVertexArray(array: vao);
285 break;
286#endif
287 case ARB:
288 case APPLE:
289 case OES:
290 vaoFuncs.helper->glBindVertexArray(array: vao);
291 break;
292 default:
293 break;
294 }
295}
296
297void QOpenGLVertexArrayObjectPrivate::release()
298{
299 switch (vaoFuncsType) {
300#ifndef QT_OPENGL_ES_2
301 case Core_3_2:
302 vaoFuncs.core_3_2->glBindVertexArray(array: 0);
303 break;
304 case Core_3_0:
305 vaoFuncs.core_3_0->glBindVertexArray(array: 0);
306 break;
307#endif
308 case ARB:
309 case APPLE:
310 case OES:
311 vaoFuncs.helper->glBindVertexArray(array: 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*/
378QOpenGLVertexArrayObject::QOpenGLVertexArrayObject(QObject* parent)
379 : QObject(*new QOpenGLVertexArrayObjectPrivate, parent)
380{
381}
382
383/*!
384 \internal
385*/
386QOpenGLVertexArrayObject::QOpenGLVertexArrayObject(QOpenGLVertexArrayObjectPrivate &dd)
387 : QObject(dd)
388{
389}
390
391/*!
392 Destroys the QOpenGLVertexArrayObject and the underlying OpenGL resource.
393*/
394QOpenGLVertexArrayObject::~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*/
415bool 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*/
425void 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*/
436bool 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*/
445GLuint 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*/
460void 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*/
469void 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
533QT_END_NAMESPACE
534
535#include "moc_qopenglvertexarrayobject.cpp"
536

source code of qtbase/src/gui/opengl/qopenglvertexarrayobject.cpp