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

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