1 | // Copyright (C) 2016 The Qt Company Ltd. |
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 <QtGui/qopengl.h> |
5 | #include <QtGui/private/qopenglcontext_p.h> |
6 | #include <QtCore/qatomic.h> |
7 | #include "qopenglbuffer.h" |
8 | #include <private/qopenglextensions_p.h> |
9 | |
10 | #ifndef GL_CONTEXT_LOST |
11 | #define GL_CONTEXT_LOST 0x0507 |
12 | #endif |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | /*! |
17 | \class QOpenGLBuffer |
18 | \brief The QOpenGLBuffer class provides functions for creating and managing OpenGL buffer objects. |
19 | \since 5.0 |
20 | \ingroup painting-3D |
21 | \inmodule QtOpenGL |
22 | |
23 | Buffer objects are created in the OpenGL server so that the |
24 | client application can avoid uploading vertices, indices, |
25 | texture image data, etc every time they are needed. |
26 | |
27 | QOpenGLBuffer objects can be copied around as a reference to the |
28 | underlying OpenGL buffer object: |
29 | |
30 | \snippet code/src_gui_opengl_qopenglbuffer.cpp 0 |
31 | |
32 | QOpenGLBuffer performs a shallow copy when objects are copied in this |
33 | manner, but does not implement copy-on-write semantics. The original |
34 | object will be affected whenever the copy is modified. |
35 | */ |
36 | |
37 | /*! |
38 | \enum QOpenGLBuffer::Type |
39 | This enum defines the type of OpenGL buffer object to create with QOpenGLBuffer. |
40 | |
41 | \value VertexBuffer Vertex buffer object for use when specifying |
42 | vertex arrays. |
43 | \value IndexBuffer Index buffer object for use with \c{glDrawElements()}. |
44 | \value PixelPackBuffer Pixel pack buffer object for reading pixel |
45 | data from the OpenGL server (for example, with \c{glReadPixels()}). |
46 | Not supported under OpenGL/ES. |
47 | \value PixelUnpackBuffer Pixel unpack buffer object for writing pixel |
48 | data to the OpenGL server (for example, with \c{glTexImage2D()}). |
49 | Not supported under OpenGL/ES. |
50 | */ |
51 | |
52 | /*! |
53 | \enum QOpenGLBuffer::UsagePattern |
54 | This enum defines the usage pattern of a QOpenGLBuffer object. |
55 | |
56 | \value StreamDraw The data will be set once and used a few times |
57 | for drawing operations. Under OpenGL/ES 1.1 this is identical |
58 | to StaticDraw. |
59 | \value StreamRead The data will be set once and used a few times |
60 | for reading data back from the OpenGL server. Not supported |
61 | under OpenGL/ES. |
62 | \value StreamCopy The data will be set once and used a few times |
63 | for reading data back from the OpenGL server for use in further |
64 | drawing operations. Not supported under OpenGL/ES. |
65 | \value StaticDraw The data will be set once and used many times |
66 | for drawing operations. |
67 | \value StaticRead The data will be set once and used many times |
68 | for reading data back from the OpenGL server. Not supported |
69 | under OpenGL/ES. |
70 | \value StaticCopy The data will be set once and used many times |
71 | for reading data back from the OpenGL server for use in further |
72 | drawing operations. Not supported under OpenGL/ES. |
73 | \value DynamicDraw The data will be modified repeatedly and used |
74 | many times for drawing operations. |
75 | \value DynamicRead The data will be modified repeatedly and used |
76 | many times for reading data back from the OpenGL server. |
77 | Not supported under OpenGL/ES. |
78 | \value DynamicCopy The data will be modified repeatedly and used |
79 | many times for reading data back from the OpenGL server for |
80 | use in further drawing operations. Not supported under OpenGL/ES. |
81 | */ |
82 | |
83 | /*! |
84 | \enum QOpenGLBuffer::Access |
85 | This enum defines the access mode for QOpenGLBuffer::map(). |
86 | |
87 | \value ReadOnly The buffer will be mapped for reading only. |
88 | \value WriteOnly The buffer will be mapped for writing only. |
89 | \value ReadWrite The buffer will be mapped for reading and writing. |
90 | */ |
91 | |
92 | /*! |
93 | \enum QOpenGLBuffer::RangeAccessFlag |
94 | This enum defines the access mode bits for QOpenGLBuffer::mapRange(). |
95 | |
96 | \value RangeRead The buffer will be mapped for reading. |
97 | \value RangeWrite The buffer will be mapped for writing. |
98 | \value RangeInvalidate Discard the previous contents of the specified range. |
99 | \value RangeInvalidateBuffer Discard the previous contents of the entire buffer. |
100 | \value RangeFlushExplicit Indicates that modifications are to be flushed explicitly via \c glFlushMappedBufferRange. |
101 | \value RangeUnsynchronized Indicates that pending operations should not be synchronized before returning from mapRange(). |
102 | */ |
103 | |
104 | class QOpenGLBufferPrivate |
105 | { |
106 | public: |
107 | QOpenGLBufferPrivate(QOpenGLBuffer::Type t) |
108 | : ref(1), |
109 | type(t), |
110 | guard(nullptr), |
111 | usagePattern(QOpenGLBuffer::StaticDraw), |
112 | actualUsagePattern(QOpenGLBuffer::StaticDraw), |
113 | funcs(nullptr) |
114 | { |
115 | } |
116 | |
117 | QAtomicInt ref; |
118 | QOpenGLBuffer::Type type; |
119 | QOpenGLSharedResourceGuard *guard; |
120 | QOpenGLBuffer::UsagePattern usagePattern; |
121 | QOpenGLBuffer::UsagePattern actualUsagePattern; |
122 | QOpenGLExtensions *funcs; |
123 | }; |
124 | |
125 | /*! |
126 | Constructs a new buffer object of type QOpenGLBuffer::VertexBuffer. |
127 | |
128 | Note: this constructor just creates the QOpenGLBuffer instance. The actual |
129 | buffer object in the OpenGL server is not created until create() is called. |
130 | |
131 | \sa create() |
132 | */ |
133 | QOpenGLBuffer::QOpenGLBuffer() |
134 | : d_ptr(new QOpenGLBufferPrivate(QOpenGLBuffer::VertexBuffer)) |
135 | { |
136 | } |
137 | |
138 | /*! |
139 | Constructs a new buffer object of \a type. |
140 | |
141 | Note: this constructor just creates the QOpenGLBuffer instance. The actual |
142 | buffer object in the OpenGL server is not created until create() is called. |
143 | |
144 | \sa create() |
145 | */ |
146 | QOpenGLBuffer::QOpenGLBuffer(QOpenGLBuffer::Type type) |
147 | : d_ptr(new QOpenGLBufferPrivate(type)) |
148 | { |
149 | } |
150 | |
151 | /*! |
152 | Constructs a shallow copy of \a other. |
153 | |
154 | Note: QOpenGLBuffer does not implement copy-on-write semantics, |
155 | so \a other will be affected whenever the copy is modified. |
156 | */ |
157 | QOpenGLBuffer::QOpenGLBuffer(const QOpenGLBuffer &other) |
158 | : d_ptr(other.d_ptr) |
159 | { |
160 | d_ptr->ref.ref(); |
161 | } |
162 | |
163 | /*! |
164 | Destroys this buffer object, including the storage being |
165 | used in the OpenGL server. |
166 | */ |
167 | QOpenGLBuffer::~QOpenGLBuffer() |
168 | { |
169 | if (d_ptr && !d_ptr->ref.deref()) { |
170 | destroy(); |
171 | delete d_ptr; |
172 | } |
173 | } |
174 | |
175 | /*! |
176 | Assigns a shallow copy of \a other to this object. |
177 | |
178 | Note: QOpenGLBuffer does not implement copy-on-write semantics, |
179 | so \a other will be affected whenever the copy is modified. |
180 | */ |
181 | QOpenGLBuffer &QOpenGLBuffer::operator=(const QOpenGLBuffer &other) |
182 | { |
183 | if (d_ptr != other.d_ptr) { |
184 | other.d_ptr->ref.ref(); |
185 | if (d_ptr && !d_ptr->ref.deref()) { |
186 | destroy(); |
187 | delete d_ptr; |
188 | } |
189 | d_ptr = other.d_ptr; |
190 | } |
191 | return *this; |
192 | } |
193 | |
194 | /*! |
195 | \fn QOpenGLBuffer::QOpenGLBuffer(QOpenGLBuffer &&other) |
196 | \since 6.5 |
197 | |
198 | Move-constructs a new QOpenGLBuffer from \a other. |
199 | |
200 | \note The moved-from object \a other is placed in a partially-formed state, |
201 | in which the only valid operations are destruction and assignment of a new |
202 | value. |
203 | */ |
204 | |
205 | /*! |
206 | \fn QOpenGLBuffer &QOpenGLBuffer::operator=(QOpenGLBuffer &&other) |
207 | \since 6.5 |
208 | |
209 | Move-assigns \a other to this QOpenGLBuffer instance. |
210 | |
211 | \note The moved-from object \a other is placed in a partially-formed state, |
212 | in which the only valid operations are destruction and assignment of a new |
213 | value. |
214 | */ |
215 | |
216 | /*! |
217 | \fn QOpenGLBuffer::swap(QOpenGLBuffer &other) |
218 | \since 6.5 |
219 | |
220 | Swaps buffer \a other with this buffer. This operation is very fast and |
221 | never fails. |
222 | */ |
223 | |
224 | /*! |
225 | Returns the type of buffer represented by this object. |
226 | */ |
227 | QOpenGLBuffer::Type QOpenGLBuffer::type() const |
228 | { |
229 | Q_D(const QOpenGLBuffer); |
230 | return d->type; |
231 | } |
232 | |
233 | /*! |
234 | Returns the usage pattern for this buffer object. |
235 | The default value is StaticDraw. |
236 | |
237 | \sa setUsagePattern() |
238 | */ |
239 | QOpenGLBuffer::UsagePattern QOpenGLBuffer::usagePattern() const |
240 | { |
241 | Q_D(const QOpenGLBuffer); |
242 | return d->usagePattern; |
243 | } |
244 | |
245 | /*! |
246 | Sets the usage pattern for this buffer object to \a value. |
247 | This function must be called before allocate() or write(). |
248 | |
249 | \sa usagePattern(), allocate(), write() |
250 | */ |
251 | void QOpenGLBuffer::setUsagePattern(QOpenGLBuffer::UsagePattern value) |
252 | { |
253 | Q_D(QOpenGLBuffer); |
254 | d->usagePattern = d->actualUsagePattern = value; |
255 | } |
256 | |
257 | namespace { |
258 | void freeBufferFunc(QOpenGLFunctions *funcs, GLuint id) |
259 | { |
260 | funcs->glDeleteBuffers(n: 1, buffers: &id); |
261 | } |
262 | } |
263 | |
264 | /*! |
265 | Creates the buffer object in the OpenGL server. Returns \c true if |
266 | the object was created; false otherwise. |
267 | |
268 | This function must be called with a current QOpenGLContext. |
269 | The buffer will be bound to and can only be used in |
270 | that context (or any other context that is shared with it). |
271 | |
272 | This function will return false if the OpenGL implementation |
273 | does not support buffers, or there is no current QOpenGLContext. |
274 | |
275 | \sa isCreated(), allocate(), write(), destroy() |
276 | */ |
277 | bool QOpenGLBuffer::create() |
278 | { |
279 | Q_D(QOpenGLBuffer); |
280 | if (d->guard && d->guard->id()) |
281 | return true; |
282 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
283 | if (ctx) { |
284 | delete d->funcs; |
285 | d->funcs = new QOpenGLExtensions(ctx); |
286 | GLuint bufferId = 0; |
287 | d->funcs->glGenBuffers(n: 1, buffers: &bufferId); |
288 | if (bufferId) { |
289 | if (d->guard) |
290 | d->guard->free(); |
291 | |
292 | d->guard = new QOpenGLSharedResourceGuard(ctx, bufferId, freeBufferFunc); |
293 | return true; |
294 | } |
295 | } |
296 | return false; |
297 | } |
298 | |
299 | /*! |
300 | Returns \c true if this buffer has been created; false otherwise. |
301 | |
302 | \sa create(), destroy() |
303 | */ |
304 | bool QOpenGLBuffer::isCreated() const |
305 | { |
306 | Q_D(const QOpenGLBuffer); |
307 | return d->guard && d->guard->id(); |
308 | } |
309 | |
310 | /*! |
311 | Destroys this buffer object, including the storage being |
312 | used in the OpenGL server. All references to the buffer will |
313 | become invalid. |
314 | */ |
315 | void QOpenGLBuffer::destroy() |
316 | { |
317 | Q_D(QOpenGLBuffer); |
318 | if (d->guard) { |
319 | d->guard->free(); |
320 | d->guard = nullptr; |
321 | } |
322 | delete d->funcs; |
323 | d->funcs = nullptr; |
324 | } |
325 | |
326 | /*! |
327 | Reads the \a count bytes in this buffer starting at \a offset |
328 | into \a data. Returns \c true on success; false if reading from |
329 | the buffer is not supported. Buffer reading is not supported |
330 | under OpenGL/ES. |
331 | |
332 | It is assumed that this buffer has been bound to the current context. |
333 | |
334 | \sa write(), bind() |
335 | */ |
336 | bool QOpenGLBuffer::read(int offset, void *data, int count) |
337 | { |
338 | #if !QT_CONFIG(opengles2) |
339 | Q_D(QOpenGLBuffer); |
340 | if (!d->funcs->hasOpenGLFeature(feature: QOpenGLFunctions::Buffers) || !d->guard->id()) |
341 | return false; |
342 | |
343 | while (true) { // Clear error state. |
344 | GLenum error = d->funcs->glGetError(); |
345 | if (error == GL_NO_ERROR) |
346 | break; |
347 | if (error == GL_CONTEXT_LOST) |
348 | return false; |
349 | }; |
350 | d->funcs->glGetBufferSubData(target: d->type, offset, size: count, data); |
351 | return d->funcs->glGetError() == GL_NO_ERROR; |
352 | #else |
353 | Q_UNUSED(offset); |
354 | Q_UNUSED(data); |
355 | Q_UNUSED(count); |
356 | return false; |
357 | #endif |
358 | } |
359 | |
360 | /*! |
361 | Replaces the \a count bytes of this buffer starting at \a offset |
362 | with the contents of \a data. Any other bytes in the buffer |
363 | will be left unmodified. |
364 | |
365 | It is assumed that create() has been called on this buffer and that |
366 | it has been bound to the current context. |
367 | |
368 | \sa create(), read(), allocate() |
369 | */ |
370 | void QOpenGLBuffer::write(int offset, const void *data, int count) |
371 | { |
372 | #ifndef QT_NO_DEBUG |
373 | if (!isCreated()) |
374 | qWarning(msg: "QOpenGLBuffer::write(): buffer not created" ); |
375 | #endif |
376 | Q_D(QOpenGLBuffer); |
377 | if (d->guard && d->guard->id()) |
378 | d->funcs->glBufferSubData(target: d->type, offset, size: count, data); |
379 | } |
380 | |
381 | /*! |
382 | Allocates \a count bytes of space to the buffer, initialized to |
383 | the contents of \a data. Any previous contents will be removed. |
384 | |
385 | It is assumed that create() has been called on this buffer and that |
386 | it has been bound to the current context. |
387 | |
388 | \sa create(), read(), write() |
389 | */ |
390 | void QOpenGLBuffer::allocate(const void *data, int count) |
391 | { |
392 | #ifndef QT_NO_DEBUG |
393 | if (!isCreated()) |
394 | qWarning(msg: "QOpenGLBuffer::allocate(): buffer not created" ); |
395 | #endif |
396 | Q_D(QOpenGLBuffer); |
397 | if (d->guard && d->guard->id()) |
398 | d->funcs->glBufferData(target: d->type, size: count, data, usage: d->actualUsagePattern); |
399 | } |
400 | |
401 | /*! |
402 | \fn void QOpenGLBuffer::allocate(int count) |
403 | \overload |
404 | |
405 | Allocates \a count bytes of space to the buffer. Any previous |
406 | contents will be removed. |
407 | |
408 | It is assumed that create() has been called on this buffer and that |
409 | it has been bound to the current context. |
410 | |
411 | \sa create(), write() |
412 | */ |
413 | |
414 | /*! |
415 | Binds the buffer associated with this object to the current |
416 | OpenGL context. Returns \c false if binding was not possible, usually because |
417 | type() is not supported on this OpenGL implementation. |
418 | |
419 | The buffer must be bound to the same QOpenGLContext current when create() |
420 | was called, or to another QOpenGLContext that is sharing with it. |
421 | Otherwise, false will be returned from this function. |
422 | |
423 | \sa release(), create() |
424 | */ |
425 | bool QOpenGLBuffer::bind() |
426 | { |
427 | #ifndef QT_NO_DEBUG |
428 | if (!isCreated()) |
429 | qWarning(msg: "QOpenGLBuffer::bind(): buffer not created" ); |
430 | #endif |
431 | Q_D(const QOpenGLBuffer); |
432 | GLuint bufferId = d->guard ? d->guard->id() : 0; |
433 | if (bufferId) { |
434 | if (d->guard->group() != QOpenGLContextGroup::currentContextGroup()) { |
435 | #ifndef QT_NO_DEBUG |
436 | qWarning(msg: "QOpenGLBuffer::bind: buffer is not valid in the current context" ); |
437 | #endif |
438 | return false; |
439 | } |
440 | d->funcs->glBindBuffer(target: d->type, buffer: bufferId); |
441 | return true; |
442 | } else { |
443 | return false; |
444 | } |
445 | } |
446 | |
447 | /*! |
448 | Releases the buffer associated with this object from the |
449 | current OpenGL context. |
450 | |
451 | This function must be called with the same QOpenGLContext current |
452 | as when bind() was called on the buffer. |
453 | |
454 | \sa bind() |
455 | */ |
456 | void QOpenGLBuffer::release() |
457 | { |
458 | #ifndef QT_NO_DEBUG |
459 | if (!isCreated()) |
460 | qWarning(msg: "QOpenGLBuffer::release(): buffer not created" ); |
461 | #endif |
462 | Q_D(const QOpenGLBuffer); |
463 | if (d->guard && d->guard->id()) |
464 | d->funcs->glBindBuffer(target: d->type, buffer: 0); |
465 | } |
466 | |
467 | /*! |
468 | Releases the buffer associated with \a type in the current |
469 | QOpenGLContext. |
470 | |
471 | This function is a direct call to \c{glBindBuffer(type, 0)} |
472 | for use when the caller does not know which QOpenGLBuffer has |
473 | been bound to the context but wants to make sure that it |
474 | is released. |
475 | |
476 | \snippet code/src_gui_opengl_qopenglbuffer.cpp 1 |
477 | */ |
478 | void QOpenGLBuffer::release(QOpenGLBuffer::Type type) |
479 | { |
480 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
481 | if (ctx) |
482 | ctx->functions()->glBindBuffer(target: GLenum(type), buffer: 0); |
483 | } |
484 | |
485 | /*! |
486 | Returns the OpenGL identifier associated with this buffer; zero if |
487 | the buffer has not been created. |
488 | |
489 | \sa isCreated() |
490 | */ |
491 | GLuint QOpenGLBuffer::bufferId() const |
492 | { |
493 | Q_D(const QOpenGLBuffer); |
494 | return d->guard ? d->guard->id() : 0; |
495 | } |
496 | |
497 | /*! |
498 | Returns the size of the data in this buffer, for reading operations. |
499 | Returns -1 if fetching the buffer size is not supported, or the |
500 | buffer has not been created. |
501 | |
502 | It is assumed that this buffer has been bound to the current context. |
503 | |
504 | \sa isCreated(), bind() |
505 | */ |
506 | int QOpenGLBuffer::size() const |
507 | { |
508 | Q_D(const QOpenGLBuffer); |
509 | if (!d->guard || !d->guard->id()) |
510 | return -1; |
511 | GLint value = -1; |
512 | d->funcs->glGetBufferParameteriv(target: d->type, GL_BUFFER_SIZE, params: &value); |
513 | return value; |
514 | } |
515 | |
516 | /*! |
517 | Maps the contents of this buffer into the application's memory |
518 | space and returns a pointer to it. Returns null if memory |
519 | mapping is not possible. The \a access parameter indicates the |
520 | type of access to be performed. |
521 | |
522 | It is assumed that create() has been called on this buffer and that |
523 | it has been bound to the current context. |
524 | |
525 | \note This function is only supported under OpenGL ES 2.0 or |
526 | earlier if the \c GL_OES_mapbuffer extension is present. |
527 | |
528 | \note On OpenGL ES 3.0 and newer, or, in case if desktop OpenGL, |
529 | if \c GL_ARB_map_buffer_range is supported, this function uses |
530 | \c glMapBufferRange instead of \c glMapBuffer. |
531 | |
532 | \sa unmap(), create(), bind(), mapRange() |
533 | */ |
534 | void *QOpenGLBuffer::map(QOpenGLBuffer::Access access) |
535 | { |
536 | Q_D(QOpenGLBuffer); |
537 | #ifndef QT_NO_DEBUG |
538 | if (!isCreated()) |
539 | qWarning(msg: "QOpenGLBuffer::map(): buffer not created" ); |
540 | #endif |
541 | if (!d->guard || !d->guard->id()) |
542 | return nullptr; |
543 | if (d->funcs->hasOpenGLExtension(extension: QOpenGLExtensions::MapBufferRange)) { |
544 | QOpenGLBuffer::RangeAccessFlags rangeAccess; |
545 | switch (access) { |
546 | case QOpenGLBuffer::ReadOnly: |
547 | rangeAccess = QOpenGLBuffer::RangeRead; |
548 | break; |
549 | case QOpenGLBuffer::WriteOnly: |
550 | rangeAccess = QOpenGLBuffer::RangeWrite; |
551 | break; |
552 | case QOpenGLBuffer::ReadWrite: |
553 | rangeAccess = QOpenGLBuffer::RangeRead | QOpenGLBuffer::RangeWrite; |
554 | break; |
555 | } |
556 | return d->funcs->glMapBufferRange(target: d->type, offset: 0, length: size(), access: rangeAccess); |
557 | } else { |
558 | return d->funcs->glMapBuffer(target: d->type, access); |
559 | } |
560 | } |
561 | |
562 | /*! |
563 | Maps the range specified by \a offset and \a count of the contents |
564 | of this buffer into the application's memory space and returns a |
565 | pointer to it. Returns null if memory mapping is not possible. |
566 | The \a access parameter specifies a combination of access flags. |
567 | |
568 | It is assumed that create() has been called on this buffer and that |
569 | it has been bound to the current context. |
570 | |
571 | \note This function is not available on OpenGL ES 2.0 and earlier. |
572 | |
573 | \sa unmap(), create(), bind() |
574 | */ |
575 | void *QOpenGLBuffer::mapRange(int offset, int count, QOpenGLBuffer::RangeAccessFlags access) |
576 | { |
577 | Q_D(QOpenGLBuffer); |
578 | #ifndef QT_NO_DEBUG |
579 | if (!isCreated()) |
580 | qWarning(msg: "QOpenGLBuffer::mapRange(): buffer not created" ); |
581 | #endif |
582 | if (!d->guard || !d->guard->id()) |
583 | return nullptr; |
584 | return d->funcs->glMapBufferRange(target: d->type, offset, length: count, access); |
585 | } |
586 | |
587 | /*! |
588 | Unmaps the buffer after it was mapped into the application's |
589 | memory space with a previous call to map(). Returns \c true if |
590 | the unmap succeeded; false otherwise. |
591 | |
592 | It is assumed that this buffer has been bound to the current context, |
593 | and that it was previously mapped with map(). |
594 | |
595 | \note This function is only supported under OpenGL ES 2.0 and |
596 | earlier if the \c{GL_OES_mapbuffer} extension is present. |
597 | |
598 | \sa map() |
599 | */ |
600 | bool QOpenGLBuffer::unmap() |
601 | { |
602 | Q_D(QOpenGLBuffer); |
603 | #ifndef QT_NO_DEBUG |
604 | if (!isCreated()) |
605 | qWarning(msg: "QOpenGLBuffer::unmap(): buffer not created" ); |
606 | #endif |
607 | if (!d->guard || !d->guard->id()) |
608 | return false; |
609 | return d->funcs->glUnmapBuffer(target: d->type) == GL_TRUE; |
610 | } |
611 | |
612 | QT_END_NAMESPACE |
613 | |