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 "qopenglframebufferobject.h" |
5 | #include "qopenglframebufferobject_p.h" |
6 | |
7 | #include <qdebug.h> |
8 | #include <private/qopengl_p.h> |
9 | #include <private/qopenglcontext_p.h> |
10 | #include <private/qopenglextensions_p.h> |
11 | #include <private/qfont_p.h> |
12 | |
13 | #include <qwindow.h> |
14 | #include <qimage.h> |
15 | #include <QtCore/qbytearray.h> |
16 | |
17 | #include <qtopengl_tracepoints_p.h> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | Q_TRACE_PREFIX(qtopengl, |
22 | "#include <private/qopengl2pexvertexarray_p.h>" \ |
23 | "#include <private/qopengltextureuploader_p.h>" \ |
24 | "#include <qopenglframebufferobject.h>" |
25 | ); |
26 | Q_TRACE_PARAM_REPLACE(GLenum, int); |
27 | Q_TRACE_PARAM_REPLACE(GLint, int); |
28 | Q_TRACE_METADATA(qtopengl, "ENUM { } QOpenGLFramebufferObject::Attachment; " ); |
29 | |
30 | #ifndef QT_NO_DEBUG |
31 | #define QT_RESET_GLERROR() \ |
32 | { \ |
33 | while (true) {\ |
34 | GLenum error = QOpenGLContext::currentContext()->functions()->glGetError(); \ |
35 | if (error == GL_NO_ERROR || error == GL_CONTEXT_LOST) \ |
36 | break; \ |
37 | } \ |
38 | } |
39 | #define QT_CHECK_GLERROR() \ |
40 | { \ |
41 | GLenum err = QOpenGLContext::currentContext()->functions()->glGetError(); \ |
42 | if (err != GL_NO_ERROR && err != GL_CONTEXT_LOST) { \ |
43 | qDebug("[%s line %d] OpenGL Error: %d", \ |
44 | __FILE__, __LINE__, (int)err); \ |
45 | } \ |
46 | } |
47 | #else |
48 | #define QT_RESET_GLERROR() {} |
49 | #define QT_CHECK_GLERROR() {} |
50 | #endif |
51 | |
52 | #ifndef GL_MAX_SAMPLES |
53 | #define GL_MAX_SAMPLES 0x8D57 |
54 | #endif |
55 | |
56 | #ifndef GL_RENDERBUFFER_SAMPLES |
57 | #define GL_RENDERBUFFER_SAMPLES 0x8CAB |
58 | #endif |
59 | |
60 | #ifndef GL_DEPTH24_STENCIL8 |
61 | #define GL_DEPTH24_STENCIL8 0x88F0 |
62 | #endif |
63 | |
64 | #ifndef GL_DEPTH_COMPONENT24 |
65 | #define GL_DEPTH_COMPONENT24 0x81A6 |
66 | #endif |
67 | |
68 | #ifndef GL_DEPTH_COMPONENT24_OES |
69 | #define GL_DEPTH_COMPONENT24_OES 0x81A6 |
70 | #endif |
71 | |
72 | #ifndef GL_READ_FRAMEBUFFER |
73 | #define GL_READ_FRAMEBUFFER 0x8CA8 |
74 | #endif |
75 | |
76 | #ifndef GL_DRAW_FRAMEBUFFER |
77 | #define GL_DRAW_FRAMEBUFFER 0x8CA9 |
78 | #endif |
79 | |
80 | #ifndef GL_RGB8 |
81 | #define GL_RGB8 0x8051 |
82 | #endif |
83 | |
84 | #ifndef GL_RGB10 |
85 | #define GL_RGB10 0x8052 |
86 | #endif |
87 | |
88 | #ifndef GL_RGB16 |
89 | #define GL_RGB16 0x8054 |
90 | #endif |
91 | |
92 | #ifndef GL_RGBA8 |
93 | #define GL_RGBA8 0x8058 |
94 | #endif |
95 | |
96 | #ifndef GL_RGB10_A2 |
97 | #define GL_RGB10_A2 0x8059 |
98 | #endif |
99 | |
100 | #ifndef GL_RGBA16 |
101 | #define GL_RGBA16 0x805B |
102 | #endif |
103 | |
104 | #ifndef GL_BGRA |
105 | #define GL_BGRA 0x80E1 |
106 | #endif |
107 | |
108 | #ifndef GL_UNSIGNED_INT_8_8_8_8_REV |
109 | #define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 |
110 | #endif |
111 | |
112 | #ifndef GL_UNSIGNED_INT_2_10_10_10_REV |
113 | #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 |
114 | #endif |
115 | |
116 | #ifndef GL_CONTEXT_LOST |
117 | #define GL_CONTEXT_LOST 0x0507 |
118 | #endif |
119 | |
120 | #ifndef GL_DEPTH_STENCIL_ATTACHMENT |
121 | #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A |
122 | #endif |
123 | |
124 | #ifndef GL_DEPTH_STENCIL |
125 | #define GL_DEPTH_STENCIL 0x84F9 |
126 | #endif |
127 | |
128 | #ifndef GL_HALF_FLOAT |
129 | #define GL_HALF_FLOAT 0x140B |
130 | #endif |
131 | |
132 | #ifndef GL_RGBA32F |
133 | #define GL_RGBA32F 0x8814 |
134 | #endif |
135 | |
136 | #ifndef GL_RGB32F |
137 | #define GL_RGB32F 0x8815 |
138 | #endif |
139 | |
140 | #ifndef GL_RGBA16F |
141 | #define GL_RGBA16F 0x881A |
142 | #endif |
143 | |
144 | #ifndef GL_RGB16F |
145 | #define GL_RGB16F 0x881B |
146 | #endif |
147 | |
148 | |
149 | /*! |
150 | \class QOpenGLFramebufferObjectFormat |
151 | \brief The QOpenGLFramebufferObjectFormat class specifies the format of an OpenGL |
152 | framebuffer object. |
153 | \inmodule QtOpenGL |
154 | |
155 | \since 5.0 |
156 | |
157 | \ingroup painting-3D |
158 | |
159 | A framebuffer object has several characteristics: |
160 | \list |
161 | \li \l{setSamples()}{Number of samples per pixels.} |
162 | \li \l{setAttachment()}{Depth and/or stencil attachments.} |
163 | \li \l{setTextureTarget()}{Texture target.} |
164 | \li \l{setInternalTextureFormat()}{Internal texture format.} |
165 | \endlist |
166 | |
167 | Note that the desired attachments or number of samples per pixels might not |
168 | be supported by the hardware driver. Call QOpenGLFramebufferObject::format() |
169 | after creating a QOpenGLFramebufferObject to find the exact format that was |
170 | used to create the frame buffer object. |
171 | |
172 | \sa QOpenGLFramebufferObject |
173 | */ |
174 | |
175 | /*! |
176 | \internal |
177 | */ |
178 | void QOpenGLFramebufferObjectFormat::detach() |
179 | { |
180 | if (d->ref.loadRelaxed() != 1) { |
181 | QOpenGLFramebufferObjectFormatPrivate *newd |
182 | = new QOpenGLFramebufferObjectFormatPrivate(d); |
183 | if (!d->ref.deref()) |
184 | delete d; |
185 | d = newd; |
186 | } |
187 | } |
188 | |
189 | /*! |
190 | Creates a QOpenGLFramebufferObjectFormat object for specifying |
191 | the format of an OpenGL framebuffer object. |
192 | |
193 | By default the format specifies a non-multisample framebuffer object with no |
194 | depth/stencil attachments, texture target \c GL_TEXTURE_2D, and internal format \c GL_RGBA8. |
195 | On OpenGL/ES systems, the default internal format is \c GL_RGBA. |
196 | |
197 | \sa samples(), attachment(), internalTextureFormat() |
198 | */ |
199 | |
200 | QOpenGLFramebufferObjectFormat::QOpenGLFramebufferObjectFormat() |
201 | { |
202 | d = new QOpenGLFramebufferObjectFormatPrivate; |
203 | } |
204 | |
205 | /*! |
206 | Constructs a copy of \a other. |
207 | */ |
208 | |
209 | QOpenGLFramebufferObjectFormat::QOpenGLFramebufferObjectFormat(const QOpenGLFramebufferObjectFormat &other) |
210 | { |
211 | d = other.d; |
212 | d->ref.ref(); |
213 | } |
214 | |
215 | /*! |
216 | Assigns \a other to this object. |
217 | */ |
218 | |
219 | QOpenGLFramebufferObjectFormat &QOpenGLFramebufferObjectFormat::operator=(const QOpenGLFramebufferObjectFormat &other) |
220 | { |
221 | if (d != other.d) { |
222 | other.d->ref.ref(); |
223 | if (!d->ref.deref()) |
224 | delete d; |
225 | d = other.d; |
226 | } |
227 | return *this; |
228 | } |
229 | |
230 | /*! |
231 | Destroys the QOpenGLFramebufferObjectFormat. |
232 | */ |
233 | QOpenGLFramebufferObjectFormat::~QOpenGLFramebufferObjectFormat() |
234 | { |
235 | if (!d->ref.deref()) |
236 | delete d; |
237 | } |
238 | |
239 | /*! |
240 | Sets the number of samples per pixel for a multisample framebuffer object |
241 | to \a samples. The default sample count of 0 represents a regular |
242 | non-multisample framebuffer object. |
243 | |
244 | If the desired amount of samples per pixel is not supported by the hardware |
245 | then the maximum number of samples per pixel will be used. Note that |
246 | multisample framebuffer objects cannot be bound as textures. Also, the |
247 | \c{GL_EXT_framebuffer_multisample} extension is required to create a |
248 | framebuffer with more than one sample per pixel. |
249 | |
250 | \sa samples() |
251 | */ |
252 | void QOpenGLFramebufferObjectFormat::setSamples(int samples) |
253 | { |
254 | detach(); |
255 | d->samples = samples; |
256 | } |
257 | |
258 | /*! |
259 | Returns the number of samples per pixel if a framebuffer object |
260 | is a multisample framebuffer object. Otherwise, returns 0. |
261 | The default value is 0. |
262 | |
263 | \sa setSamples() |
264 | */ |
265 | int QOpenGLFramebufferObjectFormat::samples() const |
266 | { |
267 | return d->samples; |
268 | } |
269 | |
270 | /*! |
271 | Enables mipmapping if \a enabled is true; otherwise disables it. |
272 | |
273 | Mipmapping is disabled by default. |
274 | |
275 | If mipmapping is enabled, additional memory will be allocated for |
276 | the mipmap levels. The mipmap levels can be updated by binding the |
277 | texture and calling glGenerateMipmap(). Mipmapping cannot be enabled |
278 | for multisampled framebuffer objects. |
279 | |
280 | \sa mipmap(), QOpenGLFramebufferObject::texture() |
281 | */ |
282 | void QOpenGLFramebufferObjectFormat::setMipmap(bool enabled) |
283 | { |
284 | detach(); |
285 | d->mipmap = enabled; |
286 | } |
287 | |
288 | /*! |
289 | Returns \c true if mipmapping is enabled. |
290 | |
291 | \sa setMipmap() |
292 | */ |
293 | bool QOpenGLFramebufferObjectFormat::mipmap() const |
294 | { |
295 | return d->mipmap; |
296 | } |
297 | |
298 | /*! |
299 | Sets the attachment configuration of a framebuffer object to \a attachment. |
300 | |
301 | \sa attachment() |
302 | */ |
303 | void QOpenGLFramebufferObjectFormat::setAttachment(QOpenGLFramebufferObject::Attachment attachment) |
304 | { |
305 | detach(); |
306 | d->attachment = attachment; |
307 | } |
308 | |
309 | /*! |
310 | Returns the configuration of the depth and stencil buffers attached to |
311 | a framebuffer object. The default is QOpenGLFramebufferObject::NoAttachment. |
312 | |
313 | \sa setAttachment() |
314 | */ |
315 | QOpenGLFramebufferObject::Attachment QOpenGLFramebufferObjectFormat::attachment() const |
316 | { |
317 | return d->attachment; |
318 | } |
319 | |
320 | /*! |
321 | Sets the texture target of the texture attached to a framebuffer object to |
322 | \a target. Ignored for multisample framebuffer objects. |
323 | |
324 | \sa textureTarget(), samples() |
325 | */ |
326 | void QOpenGLFramebufferObjectFormat::setTextureTarget(GLenum target) |
327 | { |
328 | detach(); |
329 | d->target = target; |
330 | } |
331 | |
332 | /*! |
333 | Returns the texture target of the texture attached to a framebuffer object. |
334 | Ignored for multisample framebuffer objects. The default is |
335 | \c GL_TEXTURE_2D. |
336 | |
337 | \sa setTextureTarget(), samples() |
338 | */ |
339 | GLenum QOpenGLFramebufferObjectFormat::textureTarget() const |
340 | { |
341 | return d->target; |
342 | } |
343 | |
344 | /*! |
345 | Sets the internal format of a framebuffer object's texture or |
346 | multisample framebuffer object's color buffer to |
347 | \a internalTextureFormat. |
348 | |
349 | \sa internalTextureFormat() |
350 | */ |
351 | void QOpenGLFramebufferObjectFormat::setInternalTextureFormat(GLenum internalTextureFormat) |
352 | { |
353 | detach(); |
354 | d->internal_format = internalTextureFormat; |
355 | } |
356 | |
357 | /*! |
358 | Returns the internal format of a framebuffer object's texture or |
359 | multisample framebuffer object's color buffer. The default is |
360 | \c GL_RGBA8 on desktop OpenGL systems, and \c GL_RGBA on |
361 | OpenGL/ES systems. |
362 | |
363 | \sa setInternalTextureFormat() |
364 | */ |
365 | GLenum QOpenGLFramebufferObjectFormat::internalTextureFormat() const |
366 | { |
367 | return d->internal_format; |
368 | } |
369 | |
370 | /*! |
371 | Returns \c true if all the options of this framebuffer object format |
372 | are the same as \a other; otherwise returns \c false. |
373 | */ |
374 | bool QOpenGLFramebufferObjectFormat::operator==(const QOpenGLFramebufferObjectFormat& other) const |
375 | { |
376 | if (d == other.d) |
377 | return true; |
378 | else |
379 | return d->equals(other: other.d); |
380 | } |
381 | |
382 | /*! |
383 | Returns \c false if all the options of this framebuffer object format |
384 | are the same as \a other; otherwise returns \c true. |
385 | */ |
386 | bool QOpenGLFramebufferObjectFormat::operator!=(const QOpenGLFramebufferObjectFormat& other) const |
387 | { |
388 | return !(*this == other); |
389 | } |
390 | |
391 | bool QOpenGLFramebufferObjectPrivate::checkFramebufferStatus(QOpenGLContext *ctx) const |
392 | { |
393 | if (!ctx) |
394 | return false; // Context no longer exists. |
395 | GLenum status = ctx->functions()->glCheckFramebufferStatus(GL_FRAMEBUFFER); |
396 | switch(status) { |
397 | case GL_NO_ERROR: |
398 | case GL_FRAMEBUFFER_COMPLETE: |
399 | return true; |
400 | case GL_FRAMEBUFFER_UNSUPPORTED: |
401 | qDebug(msg: "QOpenGLFramebufferObject: Unsupported framebuffer format." ); |
402 | break; |
403 | case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: |
404 | qDebug(msg: "QOpenGLFramebufferObject: Framebuffer incomplete attachment." ); |
405 | break; |
406 | case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: |
407 | qDebug(msg: "QOpenGLFramebufferObject: Framebuffer incomplete, missing attachment." ); |
408 | break; |
409 | #ifdef GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT |
410 | case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT: |
411 | qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, duplicate attachment." ); |
412 | break; |
413 | #endif |
414 | #ifdef GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS |
415 | case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: |
416 | qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, attached images must have same dimensions." ); |
417 | break; |
418 | #endif |
419 | #ifdef GL_FRAMEBUFFER_INCOMPLETE_FORMATS |
420 | case GL_FRAMEBUFFER_INCOMPLETE_FORMATS: |
421 | qDebug("QOpenGLFramebufferObject: Framebuffer incomplete, attached images must have same format." ); |
422 | break; |
423 | #endif |
424 | #ifdef GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER |
425 | case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: |
426 | qDebug(msg: "QOpenGLFramebufferObject: Framebuffer incomplete, missing draw buffer." ); |
427 | break; |
428 | #endif |
429 | #ifdef GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER |
430 | case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: |
431 | qDebug(msg: "QOpenGLFramebufferObject: Framebuffer incomplete, missing read buffer." ); |
432 | break; |
433 | #endif |
434 | #ifdef GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE |
435 | case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: |
436 | qDebug(msg: "QOpenGLFramebufferObject: Framebuffer incomplete, attachments must have same number of samples per pixel." ); |
437 | break; |
438 | #endif |
439 | default: |
440 | qDebug() <<"QOpenGLFramebufferObject: An undefined error has occurred: " << status; |
441 | break; |
442 | } |
443 | return false; |
444 | } |
445 | |
446 | namespace |
447 | { |
448 | void freeFramebufferFunc(QOpenGLFunctions *funcs, GLuint id) |
449 | { |
450 | funcs->glDeleteFramebuffers(n: 1, framebuffers: &id); |
451 | } |
452 | |
453 | void freeRenderbufferFunc(QOpenGLFunctions *funcs, GLuint id) |
454 | { |
455 | funcs->glDeleteRenderbuffers(n: 1, renderbuffers: &id); |
456 | } |
457 | |
458 | void freeTextureFunc(QOpenGLFunctions *funcs, GLuint id) |
459 | { |
460 | funcs->glDeleteTextures(n: 1, textures: &id); |
461 | } |
462 | } |
463 | |
464 | void Q_TRACE_INSTRUMENT(qtopengl) QOpenGLFramebufferObjectPrivate::init( |
465 | QOpenGLFramebufferObject *qfbo, const QSize &size, |
466 | QOpenGLFramebufferObject::Attachment attachment, |
467 | GLenum texture_target, GLenum internal_format, |
468 | GLint samples, bool mipmap) |
469 | { |
470 | Q_TRACE_SCOPE(QOpenGLFramebufferObjectPrivate_init, qfbo, size, attachment, texture_target, internal_format, samples, mipmap); |
471 | Q_UNUSED(qfbo); |
472 | |
473 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
474 | |
475 | funcs.initializeOpenGLFunctions(); |
476 | |
477 | if (!funcs.hasOpenGLFeature(feature: QOpenGLFunctions::Framebuffers)) |
478 | return; |
479 | |
480 | // Fall back to using a normal non-msaa FBO if we don't have support for MSAA |
481 | if (!funcs.hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample) |
482 | || !funcs.hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferBlit)) { |
483 | samples = 0; |
484 | } else if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) { |
485 | GLint maxSamples; |
486 | funcs.glGetIntegerv(GL_MAX_SAMPLES, params: &maxSamples); |
487 | samples = qBound(min: 0, val: int(samples), max: int(maxSamples)); |
488 | } |
489 | |
490 | colorAttachments.append(t: ColorAttachment(size, internal_format)); |
491 | |
492 | dsSize = size; |
493 | |
494 | samples = qMax(a: 0, b: samples); |
495 | requestedSamples = samples; |
496 | |
497 | target = texture_target; |
498 | |
499 | QT_RESET_GLERROR(); // reset error state |
500 | GLuint fbo = 0; |
501 | |
502 | funcs.glGenFramebuffers(n: 1, framebuffers: &fbo); |
503 | funcs.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: fbo); |
504 | |
505 | QOpenGLContextPrivate::get(context: ctx)->qgl_current_fbo_invalid = true; |
506 | |
507 | QT_CHECK_GLERROR(); |
508 | |
509 | format.setTextureTarget(target); |
510 | format.setInternalTextureFormat(internal_format); |
511 | format.setMipmap(mipmap); |
512 | |
513 | if (samples == 0) |
514 | initTexture(idx: 0); |
515 | else |
516 | initColorBuffer(idx: 0, samples: &samples); |
517 | |
518 | format.setSamples(int(samples)); |
519 | |
520 | initDepthStencilAttachments(ctx, attachment); |
521 | |
522 | if (valid) |
523 | fbo_guard = new QOpenGLSharedResourceGuard(ctx, fbo, freeFramebufferFunc); |
524 | else |
525 | funcs.glDeleteFramebuffers(n: 1, framebuffers: &fbo); |
526 | |
527 | QT_CHECK_GLERROR(); |
528 | } |
529 | |
530 | void QOpenGLFramebufferObjectPrivate::initTexture(int idx) |
531 | { |
532 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
533 | GLuint texture = 0; |
534 | |
535 | funcs.glGenTextures(n: 1, textures: &texture); |
536 | funcs.glBindTexture(target, texture); |
537 | |
538 | funcs.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
539 | funcs.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
540 | funcs.glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
541 | funcs.glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
542 | |
543 | ColorAttachment &color(colorAttachments[idx]); |
544 | |
545 | GLuint pixelType = GL_UNSIGNED_BYTE; |
546 | if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10) |
547 | pixelType = GL_UNSIGNED_INT_2_10_10_10_REV; |
548 | else if (color.internalFormat == GL_RGB16 || color.internalFormat == GL_RGBA16) |
549 | pixelType = GL_UNSIGNED_SHORT; |
550 | else if (color.internalFormat == GL_RGB16F || color.internalFormat == GL_RGBA16F) |
551 | pixelType = GL_HALF_FLOAT; |
552 | |
553 | funcs.glTexImage2D(target, level: 0, internalformat: color.internalFormat, width: color.size.width(), height: color.size.height(), border: 0, |
554 | GL_RGBA, type: pixelType, pixels: nullptr); |
555 | if (format.mipmap()) { |
556 | int width = color.size.width(); |
557 | int height = color.size.height(); |
558 | int level = 0; |
559 | while (width > 1 || height > 1) { |
560 | width = qMax(a: 1, b: width >> 1); |
561 | height = qMax(a: 1, b: height >> 1); |
562 | ++level; |
563 | funcs.glTexImage2D(target, level, internalformat: color.internalFormat, width, height, border: 0, |
564 | GL_RGBA, type: pixelType, pixels: nullptr); |
565 | } |
566 | } |
567 | funcs.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + idx, |
568 | textarget: target, texture, level: 0); |
569 | |
570 | QT_CHECK_GLERROR(); |
571 | funcs.glBindTexture(target, texture: 0); |
572 | valid = checkFramebufferStatus(ctx); |
573 | if (valid) { |
574 | color.guard = new QOpenGLSharedResourceGuard(ctx, texture, freeTextureFunc); |
575 | } else { |
576 | funcs.glDeleteTextures(n: 1, textures: &texture); |
577 | } |
578 | } |
579 | |
580 | void QOpenGLFramebufferObjectPrivate::initColorBuffer(int idx, GLint *samples) |
581 | { |
582 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
583 | GLuint color_buffer = 0; |
584 | |
585 | ColorAttachment &color(colorAttachments[idx]); |
586 | |
587 | GLenum storageFormat = color.internalFormat; |
588 | // ES requires a sized format. The older desktop extension does not. Correct the format on ES. |
589 | if (ctx->isOpenGLES()) { |
590 | if (color.internalFormat == GL_RGBA) { |
591 | if (funcs.hasOpenGLExtension(extension: QOpenGLExtensions::Sized8Formats)) |
592 | storageFormat = GL_RGBA8; |
593 | else |
594 | storageFormat = GL_RGBA4; |
595 | } else if (color.internalFormat == GL_RGB10) { |
596 | // GL_RGB10 is not allowed in ES for glRenderbufferStorage. |
597 | storageFormat = GL_RGB10_A2; |
598 | } |
599 | } |
600 | |
601 | funcs.glGenRenderbuffers(n: 1, renderbuffers: &color_buffer); |
602 | funcs.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: color_buffer); |
603 | funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples: *samples, internalformat: storageFormat, width: color.size.width(), height: color.size.height()); |
604 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + idx, |
605 | GL_RENDERBUFFER, renderbuffer: color_buffer); |
606 | |
607 | QT_CHECK_GLERROR(); |
608 | valid = checkFramebufferStatus(ctx); |
609 | if (valid) { |
610 | // Query the actual number of samples. This can be greater than the requested |
611 | // value since the typically supported values are 0, 4, 8, ..., and the |
612 | // requests are mapped to the next supported value. |
613 | funcs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, params: samples); |
614 | color.guard = new QOpenGLSharedResourceGuard(ctx, color_buffer, freeRenderbufferFunc); |
615 | } else { |
616 | funcs.glDeleteRenderbuffers(n: 1, renderbuffers: &color_buffer); |
617 | } |
618 | } |
619 | |
620 | void QOpenGLFramebufferObjectPrivate::initDepthStencilAttachments(QOpenGLContext *ctx, |
621 | QOpenGLFramebufferObject::Attachment attachment) |
622 | { |
623 | // Use the same sample count for all attachments. format.samples() already contains |
624 | // the actual number of samples for the color attachment and is not suitable. Use |
625 | // requestedSamples instead. |
626 | const int samples = requestedSamples; |
627 | |
628 | // free existing attachments |
629 | if (depth_buffer_guard) { |
630 | #ifdef Q_OS_WASM |
631 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0); |
632 | #else |
633 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer: 0); |
634 | #endif |
635 | depth_buffer_guard->free(); |
636 | } |
637 | if (stencil_buffer_guard) { |
638 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffer: 0); |
639 | if (stencil_buffer_guard != depth_buffer_guard) |
640 | stencil_buffer_guard->free(); |
641 | } |
642 | |
643 | depth_buffer_guard = nullptr; |
644 | stencil_buffer_guard = nullptr; |
645 | |
646 | GLuint depth_buffer = 0; |
647 | GLuint stencil_buffer = 0; |
648 | |
649 | // In practice, a combined depth-stencil buffer is supported by all desktop platforms, while a |
650 | // separate stencil buffer is not. On embedded devices however, a combined depth-stencil buffer |
651 | // might not be supported while separate buffers are, according to QTBUG-12861. |
652 | #ifdef Q_OS_WASM |
653 | // WebGL doesn't allow separately attach buffers to |
654 | // STENCIL_ATTACHMENT and DEPTH_ATTACHMENT |
655 | // QTBUG-69913 |
656 | if (attachment == QOpenGLFramebufferObject::CombinedDepthStencil) { |
657 | funcs.glGenRenderbuffers(1, &depth_buffer); |
658 | funcs.glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer); |
659 | Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer)); |
660 | |
661 | if (samples != 0 ) { |
662 | funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, |
663 | GL_DEPTH24_STENCIL8, dsSize.width(), dsSize.height()); |
664 | } else { |
665 | funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, |
666 | dsSize.width(), dsSize.height()); |
667 | } |
668 | |
669 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, |
670 | GL_RENDERBUFFER, depth_buffer); |
671 | |
672 | valid = checkFramebufferStatus(ctx); |
673 | if (!valid) { |
674 | funcs.glDeleteRenderbuffers(1, &depth_buffer); |
675 | depth_buffer = 0; |
676 | } |
677 | } |
678 | #else |
679 | if (attachment == QOpenGLFramebufferObject::CombinedDepthStencil |
680 | && funcs.hasOpenGLExtension(extension: QOpenGLExtensions::PackedDepthStencil)) |
681 | { |
682 | // depth and stencil buffer needs another extension |
683 | funcs.glGenRenderbuffers(n: 1, renderbuffers: &depth_buffer); |
684 | funcs.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: depth_buffer); |
685 | Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer)); |
686 | if (samples != 0 && funcs.hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample)) |
687 | funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, |
688 | GL_DEPTH24_STENCIL8, width: dsSize.width(), height: dsSize.height()); |
689 | else |
690 | funcs.glRenderbufferStorage(GL_RENDERBUFFER, |
691 | GL_DEPTH24_STENCIL8, width: dsSize.width(), height: dsSize.height()); |
692 | |
693 | stencil_buffer = depth_buffer; |
694 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
695 | GL_RENDERBUFFER, renderbuffer: depth_buffer); |
696 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, |
697 | GL_RENDERBUFFER, renderbuffer: stencil_buffer); |
698 | |
699 | valid = checkFramebufferStatus(ctx); |
700 | if (!valid) { |
701 | funcs.glDeleteRenderbuffers(n: 1, renderbuffers: &depth_buffer); |
702 | stencil_buffer = depth_buffer = 0; |
703 | } |
704 | } |
705 | |
706 | if (depth_buffer == 0 && (attachment == QOpenGLFramebufferObject::CombinedDepthStencil |
707 | || (attachment == QOpenGLFramebufferObject::Depth))) |
708 | { |
709 | funcs.glGenRenderbuffers(n: 1, renderbuffers: &depth_buffer); |
710 | funcs.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: depth_buffer); |
711 | Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer)); |
712 | if (samples != 0 && funcs.hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample)) { |
713 | if (ctx->isOpenGLES()) { |
714 | if (funcs.hasOpenGLExtension(extension: QOpenGLExtensions::Depth24)) |
715 | funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, |
716 | GL_DEPTH_COMPONENT24, width: dsSize.width(), height: dsSize.height()); |
717 | else |
718 | funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, |
719 | GL_DEPTH_COMPONENT16, width: dsSize.width(), height: dsSize.height()); |
720 | } else { |
721 | funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, |
722 | GL_DEPTH_COMPONENT, width: dsSize.width(), height: dsSize.height()); |
723 | } |
724 | } else { |
725 | if (ctx->isOpenGLES()) { |
726 | if (funcs.hasOpenGLExtension(extension: QOpenGLExtensions::Depth24)) { |
727 | funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, |
728 | width: dsSize.width(), height: dsSize.height()); |
729 | } else { |
730 | funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, |
731 | width: dsSize.width(), height: dsSize.height()); |
732 | } |
733 | } else { |
734 | funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width: dsSize.width(), height: dsSize.height()); |
735 | } |
736 | } |
737 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
738 | GL_RENDERBUFFER, renderbuffer: depth_buffer); |
739 | valid = checkFramebufferStatus(ctx); |
740 | if (!valid) { |
741 | funcs.glDeleteRenderbuffers(n: 1, renderbuffers: &depth_buffer); |
742 | depth_buffer = 0; |
743 | } |
744 | } |
745 | |
746 | if (stencil_buffer == 0 && (attachment == QOpenGLFramebufferObject::CombinedDepthStencil)) { |
747 | funcs.glGenRenderbuffers(n: 1, renderbuffers: &stencil_buffer); |
748 | funcs.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: stencil_buffer); |
749 | Q_ASSERT(funcs.glIsRenderbuffer(stencil_buffer)); |
750 | |
751 | #if QT_CONFIG(opengles2) |
752 | GLenum storage = GL_STENCIL_INDEX8; |
753 | #else |
754 | GLenum storage = ctx->isOpenGLES() ? GL_STENCIL_INDEX8 : GL_STENCIL_INDEX; |
755 | #endif |
756 | |
757 | if (samples != 0 && funcs.hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample)) |
758 | funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat: storage, width: dsSize.width(), height: dsSize.height()); |
759 | else |
760 | funcs.glRenderbufferStorage(GL_RENDERBUFFER, internalformat: storage, width: dsSize.width(), height: dsSize.height()); |
761 | |
762 | funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, |
763 | GL_RENDERBUFFER, renderbuffer: stencil_buffer); |
764 | valid = checkFramebufferStatus(ctx); |
765 | if (!valid) { |
766 | funcs.glDeleteRenderbuffers(n: 1, renderbuffers: &stencil_buffer); |
767 | stencil_buffer = 0; |
768 | } |
769 | } |
770 | #endif //Q_OS_WASM |
771 | |
772 | // The FBO might have become valid after removing the depth or stencil buffer. |
773 | valid = checkFramebufferStatus(ctx); |
774 | |
775 | #ifdef Q_OS_WASM |
776 | if (depth_buffer) { |
777 | #else |
778 | if (depth_buffer && stencil_buffer) { |
779 | #endif |
780 | fbo_attachment = QOpenGLFramebufferObject::CombinedDepthStencil; |
781 | } else if (depth_buffer) { |
782 | fbo_attachment = QOpenGLFramebufferObject::Depth; |
783 | } else { |
784 | fbo_attachment = QOpenGLFramebufferObject::NoAttachment; |
785 | } |
786 | |
787 | if (valid) { |
788 | if (depth_buffer) |
789 | depth_buffer_guard = new QOpenGLSharedResourceGuard(ctx, depth_buffer, freeRenderbufferFunc); |
790 | if (stencil_buffer) { |
791 | if (stencil_buffer == depth_buffer) |
792 | stencil_buffer_guard = depth_buffer_guard; |
793 | else |
794 | stencil_buffer_guard = new QOpenGLSharedResourceGuard(ctx, stencil_buffer, freeRenderbufferFunc); |
795 | } |
796 | } else { |
797 | if (depth_buffer) |
798 | funcs.glDeleteRenderbuffers(n: 1, renderbuffers: &depth_buffer); |
799 | if (stencil_buffer && depth_buffer != stencil_buffer) |
800 | funcs.glDeleteRenderbuffers(n: 1, renderbuffers: &stencil_buffer); |
801 | } |
802 | QT_CHECK_GLERROR(); |
803 | |
804 | format.setAttachment(fbo_attachment); |
805 | } |
806 | |
807 | /*! |
808 | \class QOpenGLFramebufferObject |
809 | \brief The QOpenGLFramebufferObject class encapsulates an OpenGL framebuffer object. |
810 | \since 5.0 |
811 | \inmodule QtOpenGL |
812 | |
813 | \ingroup painting-3D |
814 | |
815 | The QOpenGLFramebufferObject class encapsulates an OpenGL framebuffer |
816 | object, defined by the \c{GL_EXT_framebuffer_object} extension. It provides |
817 | a rendering surface that can be painted on with a QPainter with the help of |
818 | QOpenGLPaintDevice, or rendered to using native OpenGL calls. This surface |
819 | can be bound and used as a regular texture in your own OpenGL drawing code. |
820 | By default, the QOpenGLFramebufferObject class generates a 2D OpenGL |
821 | texture (using the \c{GL_TEXTURE_2D} target), which is used as the internal |
822 | rendering target. |
823 | |
824 | \b{It is important to have a current OpenGL context when creating a |
825 | QOpenGLFramebufferObject, otherwise initialization will fail.} |
826 | |
827 | Create the QOpenGLFrameBufferObject instance with the CombinedDepthStencil |
828 | attachment if you want QPainter to render correctly. Note that you need to |
829 | create a QOpenGLFramebufferObject with more than one sample per pixel for |
830 | primitives to be antialiased when drawing using a QPainter. To create a |
831 | multisample framebuffer object you should use one of the constructors that |
832 | take a QOpenGLFramebufferObjectFormat parameter, and set the |
833 | QOpenGLFramebufferObjectFormat::samples() property to a non-zero value. |
834 | |
835 | For multisample framebuffer objects a color render buffer is created, |
836 | otherwise a texture with the specified texture target is created. |
837 | The color render buffer or texture will have the specified internal |
838 | format, and will be bound to the \c GL_COLOR_ATTACHMENT0 |
839 | attachment in the framebuffer object. |
840 | |
841 | Multiple render targets are also supported, in case the OpenGL |
842 | implementation supports this. Here there will be multiple textures (or, in |
843 | case of multisampling, renderbuffers) present and each of them will get |
844 | attached to \c GL_COLOR_ATTACHMENT0, \c 1, \c 2, ... |
845 | |
846 | If you want to use a framebuffer object with multisampling enabled |
847 | as a texture, you first need to copy from it to a regular framebuffer |
848 | object using QOpenGLContext::blitFramebuffer(). |
849 | |
850 | It is possible to draw into a QOpenGLFramebufferObject using QPainter and |
851 | QOpenGLPaintDevice in a separate thread. |
852 | */ |
853 | |
854 | |
855 | /*! |
856 | \enum QOpenGLFramebufferObject::Attachment |
857 | |
858 | This enum type is used to configure the depth and stencil buffers |
859 | attached to the framebuffer object when it is created. |
860 | |
861 | \value NoAttachment No attachment is added to the framebuffer object. Note that the |
862 | OpenGL depth and stencil tests won't work when rendering to a |
863 | framebuffer object without any depth or stencil buffers. |
864 | This is the default value. |
865 | |
866 | \value CombinedDepthStencil If the \c GL_EXT_packed_depth_stencil extension is present, |
867 | a combined depth and stencil buffer is attached. |
868 | If the extension is not present, only a depth buffer is attached. |
869 | |
870 | \value Depth A depth buffer is attached to the framebuffer object. |
871 | |
872 | \sa attachment() |
873 | */ |
874 | |
875 | static inline GLenum effectiveInternalFormat(GLenum internalFormat) |
876 | { |
877 | if (!internalFormat) |
878 | #if QT_CONFIG(opengles2) |
879 | internalFormat = GL_RGBA; |
880 | #else |
881 | internalFormat = QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8; |
882 | #endif |
883 | return internalFormat; |
884 | } |
885 | |
886 | /*! |
887 | |
888 | Constructs an OpenGL framebuffer object and binds a 2D OpenGL texture |
889 | to the buffer of the size \a size. The texture is bound to the |
890 | \c GL_COLOR_ATTACHMENT0 target in the framebuffer object. |
891 | |
892 | The \a target parameter is used to specify the OpenGL texture |
893 | target. The default target is \c GL_TEXTURE_2D. Keep in mind that |
894 | \c GL_TEXTURE_2D textures must have a power of 2 width and height |
895 | (e.g. 256x512), unless you are using OpenGL 2.0 or higher. |
896 | |
897 | By default, no depth and stencil buffers are attached. This behavior |
898 | can be toggled using one of the overloaded constructors. |
899 | |
900 | The default internal texture format is \c GL_RGBA8 for desktop |
901 | OpenGL, and \c GL_RGBA for OpenGL/ES. |
902 | |
903 | It is important that you have a current OpenGL context set when |
904 | creating the QOpenGLFramebufferObject, otherwise the initialization |
905 | will fail. |
906 | |
907 | \sa size(), texture(), attachment() |
908 | */ |
909 | |
910 | QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, GLenum target) |
911 | : d_ptr(new QOpenGLFramebufferObjectPrivate) |
912 | { |
913 | Q_D(QOpenGLFramebufferObject); |
914 | d->init(qfbo: this, size, attachment: NoAttachment, texture_target: target, internal_format: effectiveInternalFormat(internalFormat: 0)); |
915 | } |
916 | |
917 | /*! |
918 | |
919 | Constructs an OpenGL framebuffer object and binds a 2D OpenGL texture |
920 | to the buffer of the given \a width and \a height. |
921 | |
922 | \sa size(), texture() |
923 | */ |
924 | QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, GLenum target) |
925 | : QOpenGLFramebufferObject(QSize(width, height), target) |
926 | { |
927 | } |
928 | |
929 | /*! |
930 | |
931 | Constructs an OpenGL framebuffer object of the given \a size based on the |
932 | supplied \a format. |
933 | */ |
934 | |
935 | QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, const QOpenGLFramebufferObjectFormat &format) |
936 | : d_ptr(new QOpenGLFramebufferObjectPrivate) |
937 | { |
938 | Q_D(QOpenGLFramebufferObject); |
939 | d->init(qfbo: this, size, attachment: format.attachment(), texture_target: format.textureTarget(), internal_format: format.internalTextureFormat(), |
940 | samples: format.samples(), mipmap: format.mipmap()); |
941 | } |
942 | |
943 | /*! |
944 | |
945 | Constructs an OpenGL framebuffer object of the given \a width and \a height |
946 | based on the supplied \a format. |
947 | */ |
948 | |
949 | QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, const QOpenGLFramebufferObjectFormat &format) |
950 | : QOpenGLFramebufferObject(QSize(width, height), format) |
951 | { |
952 | } |
953 | |
954 | /*! |
955 | |
956 | Constructs an OpenGL framebuffer object and binds a texture to the |
957 | buffer of the given \a width and \a height. |
958 | |
959 | The \a attachment parameter describes the depth/stencil buffer |
960 | configuration, \a target the texture target and \a internalFormat |
961 | the internal texture format. The default texture target is \c |
962 | GL_TEXTURE_2D, while the default internal format is \c GL_RGBA8 |
963 | for desktop OpenGL and \c GL_RGBA for OpenGL/ES. |
964 | |
965 | \sa size(), texture(), attachment() |
966 | */ |
967 | QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, Attachment attachment, |
968 | GLenum target, GLenum internalFormat) |
969 | : d_ptr(new QOpenGLFramebufferObjectPrivate) |
970 | { |
971 | Q_D(QOpenGLFramebufferObject); |
972 | d->init(qfbo: this, size: QSize(width, height), attachment, texture_target: target, internal_format: effectiveInternalFormat(internalFormat)); |
973 | } |
974 | |
975 | /*! |
976 | |
977 | Constructs an OpenGL framebuffer object and binds a texture to the |
978 | buffer of the given \a size. |
979 | |
980 | The \a attachment parameter describes the depth/stencil buffer |
981 | configuration, \a target the texture target and \a internalFormat |
982 | the internal texture format. The default texture target is \c |
983 | GL_TEXTURE_2D, while the default internal format is \c GL_RGBA8 |
984 | for desktop OpenGL and \c GL_RGBA for OpenGL/ES. |
985 | |
986 | \sa size(), texture(), attachment() |
987 | */ |
988 | QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, Attachment attachment, |
989 | GLenum target, GLenum internalFormat) |
990 | : d_ptr(new QOpenGLFramebufferObjectPrivate) |
991 | { |
992 | Q_D(QOpenGLFramebufferObject); |
993 | d->init(qfbo: this, size, attachment, texture_target: target, internal_format: effectiveInternalFormat(internalFormat)); |
994 | } |
995 | |
996 | /*! |
997 | |
998 | Destroys the framebuffer object and frees any allocated resources. |
999 | */ |
1000 | QOpenGLFramebufferObject::~QOpenGLFramebufferObject() |
1001 | { |
1002 | Q_D(QOpenGLFramebufferObject); |
1003 | if (isBound()) |
1004 | release(); |
1005 | |
1006 | for (const auto &color : std::as_const(t&: d->colorAttachments)) { |
1007 | if (color.guard) |
1008 | color.guard->free(); |
1009 | } |
1010 | d->colorAttachments.clear(); |
1011 | |
1012 | if (d->depth_buffer_guard) |
1013 | d->depth_buffer_guard->free(); |
1014 | if (d->stencil_buffer_guard && d->stencil_buffer_guard != d->depth_buffer_guard) |
1015 | d->stencil_buffer_guard->free(); |
1016 | if (d->fbo_guard) |
1017 | d->fbo_guard->free(); |
1018 | |
1019 | QOpenGLContextPrivate *contextPrv = QOpenGLContextPrivate::get(context: QOpenGLContext::currentContext()); |
1020 | if (contextPrv && contextPrv->qgl_current_fbo == this) { |
1021 | contextPrv->qgl_current_fbo_invalid = true; |
1022 | contextPrv->qgl_current_fbo = nullptr; |
1023 | } |
1024 | } |
1025 | |
1026 | /*! |
1027 | Creates and attaches an additional texture or renderbuffer of \a size width |
1028 | and height. |
1029 | |
1030 | There is always an attachment at GL_COLOR_ATTACHMENT0. Call this function |
1031 | to set up additional attachments at GL_COLOR_ATTACHMENT1, |
1032 | GL_COLOR_ATTACHMENT2, ... |
1033 | |
1034 | When \a internalFormat is not \c 0, it specifies the internal format of the |
1035 | texture or renderbuffer. Otherwise a default of GL_RGBA or GL_RGBA8 is |
1036 | used. |
1037 | |
1038 | \note This is only functional when multiple render targets are supported by |
1039 | the OpenGL implementation. When that is not the case, the function will not |
1040 | add any additional color attachments. Call |
1041 | QOpenGLFunctions::hasOpenGLFeature() with |
1042 | QOpenGLFunctions::MultipleRenderTargets at runtime to check if MRT is |
1043 | supported. |
1044 | |
1045 | \note The internal format of the color attachments may differ but there may |
1046 | be limitations on the supported combinations, depending on the drivers. |
1047 | |
1048 | \note The size of the color attachments may differ but rendering is limited |
1049 | to the area that fits all the attachments, according to the OpenGL |
1050 | specification. Some drivers may not be fully conformant in this respect, |
1051 | however. |
1052 | |
1053 | \since 5.6 |
1054 | */ |
1055 | void QOpenGLFramebufferObject::addColorAttachment(const QSize &size, GLenum internalFormat) |
1056 | { |
1057 | Q_D(QOpenGLFramebufferObject); |
1058 | |
1059 | if (!QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(feature: QOpenGLFunctions::MultipleRenderTargets)) { |
1060 | qWarning(msg: "Multiple render targets not supported, ignoring extra color attachment request" ); |
1061 | return; |
1062 | } |
1063 | |
1064 | QOpenGLFramebufferObjectPrivate::ColorAttachment color(size, effectiveInternalFormat(internalFormat)); |
1065 | d->colorAttachments.append(t: color); |
1066 | const int idx = d->colorAttachments.size() - 1; |
1067 | |
1068 | if (d->requestedSamples == 0) { |
1069 | d->initTexture(idx); |
1070 | } else { |
1071 | GLint samples = d->requestedSamples; |
1072 | d->initColorBuffer(idx, samples: &samples); |
1073 | } |
1074 | } |
1075 | |
1076 | /*! \overload |
1077 | |
1078 | Creates and attaches an additional texture or renderbuffer of size \a width and \a height. |
1079 | |
1080 | When \a internalFormat is not \c 0, it specifies the internal format of the texture or |
1081 | renderbuffer. Otherwise a default of GL_RGBA or GL_RGBA8 is used. |
1082 | |
1083 | \since 5.6 |
1084 | */ |
1085 | void QOpenGLFramebufferObject::addColorAttachment(int width, int height, GLenum internalFormat) |
1086 | { |
1087 | addColorAttachment(size: QSize(width, height), internalFormat); |
1088 | } |
1089 | |
1090 | /*! |
1091 | \fn bool QOpenGLFramebufferObject::isValid() const |
1092 | |
1093 | Returns \c true if the framebuffer object is valid. |
1094 | |
1095 | The framebuffer can become invalid if the initialization process |
1096 | fails, the user attaches an invalid buffer to the framebuffer |
1097 | object, or a non-power of two width/height is specified as the |
1098 | texture size if the texture target is \c{GL_TEXTURE_2D}. |
1099 | The non-power of two limitation does not apply if the OpenGL version |
1100 | is 2.0 or higher, or if the GL_ARB_texture_non_power_of_two extension |
1101 | is present. |
1102 | |
1103 | The framebuffer can also become invalid if the QOpenGLContext that |
1104 | the framebuffer was created within is destroyed and there are |
1105 | no other shared contexts that can take over ownership of the |
1106 | framebuffer. |
1107 | */ |
1108 | bool QOpenGLFramebufferObject::isValid() const |
1109 | { |
1110 | Q_D(const QOpenGLFramebufferObject); |
1111 | return d->valid && d->fbo_guard && d->fbo_guard->id(); |
1112 | } |
1113 | |
1114 | /*! |
1115 | \fn bool QOpenGLFramebufferObject::bind() |
1116 | |
1117 | Switches rendering from the default, windowing system provided |
1118 | framebuffer to this framebuffer object. |
1119 | Returns \c true upon success, false otherwise. |
1120 | |
1121 | \note If takeTexture() was called, a new texture is created and associated |
1122 | with the framebuffer object. This is potentially expensive and changes the |
1123 | context state (the currently bound texture). |
1124 | |
1125 | \sa release() |
1126 | */ |
1127 | bool QOpenGLFramebufferObject::bind() |
1128 | { |
1129 | if (!isValid()) |
1130 | return false; |
1131 | Q_D(QOpenGLFramebufferObject); |
1132 | QOpenGLContext *current = QOpenGLContext::currentContext(); |
1133 | if (!current) |
1134 | return false; |
1135 | #ifdef QT_DEBUG |
1136 | if (current->shareGroup() != d->fbo_guard->group()) |
1137 | qWarning(msg: "QOpenGLFramebufferObject::bind() called from incompatible context" ); |
1138 | #endif |
1139 | |
1140 | d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: d->fbo()); |
1141 | |
1142 | QOpenGLContextPrivate::get(context: current)->qgl_current_fbo_invalid = true; |
1143 | QOpenGLContextPrivate::get(context: current)->qgl_current_fbo = this; |
1144 | |
1145 | if (d->format.samples() == 0) { |
1146 | // Create new textures to replace the ones stolen via takeTexture(). |
1147 | for (int i = 0; i < d->colorAttachments.size(); ++i) { |
1148 | if (!d->colorAttachments.at(idx: i).guard) |
1149 | d->initTexture(idx: i); |
1150 | } |
1151 | } |
1152 | |
1153 | return d->valid; |
1154 | } |
1155 | |
1156 | /*! |
1157 | \fn bool QOpenGLFramebufferObject::release() |
1158 | |
1159 | Switches rendering back to the default, windowing system provided |
1160 | framebuffer. |
1161 | Returns \c true upon success, false otherwise. |
1162 | |
1163 | \sa bind() |
1164 | */ |
1165 | bool QOpenGLFramebufferObject::release() |
1166 | { |
1167 | if (!isValid()) |
1168 | return false; |
1169 | |
1170 | QOpenGLContext *current = QOpenGLContext::currentContext(); |
1171 | if (!current) |
1172 | return false; |
1173 | |
1174 | Q_D(QOpenGLFramebufferObject); |
1175 | #ifdef QT_DEBUG |
1176 | if (current->shareGroup() != d->fbo_guard->group()) |
1177 | qWarning(msg: "QOpenGLFramebufferObject::release() called from incompatible context" ); |
1178 | #endif |
1179 | |
1180 | if (current) { |
1181 | d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: current->defaultFramebufferObject()); |
1182 | |
1183 | QOpenGLContextPrivate *contextPrv = QOpenGLContextPrivate::get(context: current); |
1184 | contextPrv->qgl_current_fbo_invalid = true; |
1185 | contextPrv->qgl_current_fbo = nullptr; |
1186 | } |
1187 | |
1188 | return true; |
1189 | } |
1190 | |
1191 | /*! |
1192 | \fn GLuint QOpenGLFramebufferObject::texture() const |
1193 | |
1194 | Returns the texture id for the texture attached as the default |
1195 | rendering target in this framebuffer object. This texture id can |
1196 | be bound as a normal texture in your own OpenGL code. |
1197 | |
1198 | If a multisample framebuffer object is used then the value returned |
1199 | from this function will be invalid. |
1200 | |
1201 | When multiple textures are attached, the return value is the ID of |
1202 | the first one. |
1203 | |
1204 | \sa takeTexture(), textures() |
1205 | */ |
1206 | GLuint QOpenGLFramebufferObject::texture() const |
1207 | { |
1208 | Q_D(const QOpenGLFramebufferObject); |
1209 | return d->colorAttachments[0].guard ? d->colorAttachments[0].guard->id() : 0; |
1210 | } |
1211 | |
1212 | /*! |
1213 | Returns the texture id for all attached textures. |
1214 | |
1215 | If a multisample framebuffer object is used, then an empty vector is returned. |
1216 | |
1217 | \since 5.6 |
1218 | |
1219 | \sa takeTexture(), texture() |
1220 | */ |
1221 | QList<GLuint> QOpenGLFramebufferObject::textures() const |
1222 | { |
1223 | Q_D(const QOpenGLFramebufferObject); |
1224 | QList<GLuint> ids; |
1225 | if (d->format.samples() != 0) |
1226 | return ids; |
1227 | ids.reserve(asize: d->colorAttachments.size()); |
1228 | for (const auto &color : d->colorAttachments) |
1229 | ids.append(t: color.guard ? color.guard->id() : 0); |
1230 | return ids; |
1231 | } |
1232 | |
1233 | /*! |
1234 | \fn GLuint QOpenGLFramebufferObject::takeTexture() |
1235 | |
1236 | Returns the texture id for the texture attached to this framebuffer |
1237 | object. The ownership of the texture is transferred to the caller. |
1238 | |
1239 | If the framebuffer object is currently bound, an implicit release() |
1240 | will be done. During the next call to bind() a new texture will be |
1241 | created. |
1242 | |
1243 | If a multisample framebuffer object is used, then there is no |
1244 | texture and the return value from this function will be invalid. |
1245 | Similarly, incomplete framebuffer objects will also return 0. |
1246 | |
1247 | \since 5.3 |
1248 | |
1249 | \sa texture(), bind(), release() |
1250 | */ |
1251 | GLuint QOpenGLFramebufferObject::takeTexture() |
1252 | { |
1253 | return takeTexture(colorAttachmentIndex: 0); |
1254 | } |
1255 | |
1256 | /*! \overload |
1257 | |
1258 | Returns the texture id for the texture attached to the color attachment of |
1259 | index \a colorAttachmentIndex of this framebuffer object. The ownership of |
1260 | the texture is transferred to the caller. |
1261 | |
1262 | When \a colorAttachmentIndex is \c 0, the behavior is identical to the |
1263 | parameter-less variant of this function. |
1264 | |
1265 | If the framebuffer object is currently bound, an implicit release() |
1266 | will be done. During the next call to bind() a new texture will be |
1267 | created. |
1268 | |
1269 | If a multisample framebuffer object is used, then there is no |
1270 | texture and the return value from this function will be invalid. |
1271 | Similarly, incomplete framebuffer objects will also return 0. |
1272 | |
1273 | \since 5.6 |
1274 | */ |
1275 | GLuint QOpenGLFramebufferObject::takeTexture(int colorAttachmentIndex) |
1276 | { |
1277 | Q_D(QOpenGLFramebufferObject); |
1278 | GLuint id = 0; |
1279 | if (isValid() && d->format.samples() == 0 && d->colorAttachments.size() > colorAttachmentIndex) { |
1280 | QOpenGLContext *current = QOpenGLContext::currentContext(); |
1281 | if (current && current->shareGroup() == d->fbo_guard->group() && isBound()) |
1282 | release(); |
1283 | auto &guard = d->colorAttachments[colorAttachmentIndex].guard; |
1284 | id = guard ? guard->id() : 0; |
1285 | // Do not call free() on texture_guard, just null it out. |
1286 | // This way the texture will not be deleted when the guard is destroyed. |
1287 | guard = nullptr; |
1288 | } |
1289 | return id; |
1290 | } |
1291 | |
1292 | /*! |
1293 | \return the size of the color and depth/stencil attachments attached to |
1294 | this framebuffer object. |
1295 | */ |
1296 | QSize QOpenGLFramebufferObject::size() const |
1297 | { |
1298 | Q_D(const QOpenGLFramebufferObject); |
1299 | return d->dsSize; |
1300 | } |
1301 | |
1302 | /*! |
1303 | \return the sizes of all color attachments attached to this framebuffer |
1304 | object. |
1305 | |
1306 | \since 5.6 |
1307 | */ |
1308 | QList<QSize> QOpenGLFramebufferObject::sizes() const |
1309 | { |
1310 | Q_D(const QOpenGLFramebufferObject); |
1311 | QList<QSize> sz; |
1312 | sz.reserve(asize: d->colorAttachments.size()); |
1313 | for (const auto &color : d->colorAttachments) |
1314 | sz.append(t: color.size); |
1315 | return sz; |
1316 | } |
1317 | |
1318 | /*! |
1319 | \fn int QOpenGLFramebufferObject::width() const |
1320 | |
1321 | Returns the width of the framebuffer object attachments. |
1322 | */ |
1323 | |
1324 | /*! |
1325 | \fn int QOpenGLFramebufferObject::height() const |
1326 | |
1327 | Returns the height of the framebuffer object attachments. |
1328 | */ |
1329 | |
1330 | /*! |
1331 | Returns the format of this framebuffer object. |
1332 | */ |
1333 | QOpenGLFramebufferObjectFormat QOpenGLFramebufferObject::format() const |
1334 | { |
1335 | Q_D(const QOpenGLFramebufferObject); |
1336 | return d->format; |
1337 | } |
1338 | |
1339 | static inline QImage qt_gl_read_framebuffer_rgba8(const QSize &size, bool include_alpha, QOpenGLContext *context) |
1340 | { |
1341 | QOpenGLFunctions *funcs = context->functions(); |
1342 | const int w = size.width(); |
1343 | const int h = size.height(); |
1344 | bool isOpenGL12orBetter = !context->isOpenGLES() && (context->format().majorVersion() >= 2 || context->format().minorVersion() >= 2); |
1345 | if (isOpenGL12orBetter) { |
1346 | QImage img(size, include_alpha ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32); |
1347 | if (!img.isNull()) |
1348 | funcs->glReadPixels(x: 0, y: 0, width: w, height: h, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels: img.bits()); |
1349 | return img; |
1350 | } |
1351 | |
1352 | // For OpenGL ES stick with the byte ordered format / RGBA readback format |
1353 | // since that is the only spec mandated way. (also, skip the |
1354 | // GL_IMPLEMENTATION_COLOR_READ_FORMAT mess since there is nothing saying a |
1355 | // BGRA capable impl would return BGRA from there) |
1356 | |
1357 | QImage rgbaImage(size, include_alpha ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBX8888); |
1358 | if (!rgbaImage.isNull()) |
1359 | funcs->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_UNSIGNED_BYTE, pixels: rgbaImage.bits()); |
1360 | return rgbaImage; |
1361 | } |
1362 | |
1363 | static inline QImage qt_gl_read_framebuffer_rgb10a2(const QSize &size, bool include_alpha, QOpenGLContext *context) |
1364 | { |
1365 | // We assume OpenGL 1.2+ or ES 3.0+ here. |
1366 | QImage img(size, include_alpha ? QImage::Format_A2BGR30_Premultiplied : QImage::Format_BGR30); |
1367 | if (!img.isNull()) |
1368 | context->functions()->glReadPixels(x: 0, y: 0, width: size.width(), height: size.height(), GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, pixels: img.bits()); |
1369 | return img; |
1370 | } |
1371 | |
1372 | static inline QImage qt_gl_read_framebuffer_rgba16(const QSize &size, bool include_alpha, QOpenGLContext *context) |
1373 | { |
1374 | // We assume OpenGL 1.2+ or ES 3.0+ here. |
1375 | QImage img(size, include_alpha ? QImage::Format_RGBA64_Premultiplied : QImage::Format_RGBX64); |
1376 | if (!img.isNull()) |
1377 | context->functions()->glReadPixels(x: 0, y: 0, width: size.width(), height: size.height(), GL_RGBA, GL_UNSIGNED_SHORT, pixels: img.bits()); |
1378 | return img; |
1379 | } |
1380 | |
1381 | static inline QImage qt_gl_read_framebuffer_rgba16f(const QSize &size, bool include_alpha, QOpenGLContext *context) |
1382 | { |
1383 | // We assume OpenGL (ES) 3.0+ here. |
1384 | QImage img(size, include_alpha ? QImage::Format_RGBA16FPx4_Premultiplied : QImage::Format_RGBX16FPx4); |
1385 | if (!img.isNull()) |
1386 | context->functions()->glReadPixels(x: 0, y: 0, width: size.width(), height: size.height(), GL_RGBA, GL_HALF_FLOAT, pixels: img.bits()); |
1387 | return img; |
1388 | } |
1389 | |
1390 | static inline QImage qt_gl_read_framebuffer_rgba32f(const QSize &size, bool include_alpha, QOpenGLContext *context) |
1391 | { |
1392 | QImage img(size, include_alpha ? QImage::Format_RGBA32FPx4_Premultiplied : QImage::Format_RGBX32FPx4); |
1393 | if (!img.isNull()) |
1394 | context->functions()->glReadPixels(x: 0, y: 0, width: size.width(), height: size.height(), GL_RGBA, GL_FLOAT, pixels: img.bits()); |
1395 | return img; |
1396 | } |
1397 | |
1398 | static QImage qt_gl_read_framebuffer(const QSize &size, GLenum internal_format, bool include_alpha, bool flip) |
1399 | { |
1400 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
1401 | QOpenGLFunctions *funcs = ctx->functions(); |
1402 | while (true) { |
1403 | GLenum error = funcs->glGetError(); |
1404 | if (error == GL_NO_ERROR || error == GL_CONTEXT_LOST) |
1405 | break; |
1406 | } |
1407 | switch (internal_format) { |
1408 | case GL_RGB: |
1409 | case GL_RGB8: |
1410 | return qt_gl_read_framebuffer_rgba8(size, include_alpha: false, context: ctx).mirrored(horizontally: false, vertically: flip); |
1411 | case GL_RGB10: |
1412 | return qt_gl_read_framebuffer_rgb10a2(size, include_alpha: false, context: ctx).mirrored(horizontally: false, vertically: flip); |
1413 | case GL_RGB10_A2: |
1414 | return qt_gl_read_framebuffer_rgb10a2(size, include_alpha, context: ctx).mirrored(horizontally: false, vertically: flip); |
1415 | case GL_RGB16: |
1416 | return qt_gl_read_framebuffer_rgba16(size, include_alpha: false, context: ctx).mirrored(horizontally: false, vertically: flip); |
1417 | case GL_RGBA16: |
1418 | return qt_gl_read_framebuffer_rgba16(size, include_alpha, context: ctx).mirrored(horizontally: false, vertically: flip); |
1419 | case GL_RGB16F: |
1420 | return qt_gl_read_framebuffer_rgba16f(size, include_alpha: false, context: ctx).mirrored(horizontally: false, vertically: flip); |
1421 | case GL_RGBA16F: |
1422 | return qt_gl_read_framebuffer_rgba16f(size, include_alpha, context: ctx).mirrored(horizontally: false, vertically: flip); |
1423 | case GL_RGB32F: |
1424 | return qt_gl_read_framebuffer_rgba32f(size, include_alpha: false, context: ctx).mirrored(horizontally: false, vertically: flip); |
1425 | case GL_RGBA32F: |
1426 | return qt_gl_read_framebuffer_rgba32f(size, include_alpha, context: ctx).mirrored(horizontally: false, vertically: flip); |
1427 | case GL_RGBA: |
1428 | case GL_RGBA8: |
1429 | default: |
1430 | return qt_gl_read_framebuffer_rgba8(size, include_alpha, context: ctx).mirrored(horizontally: false, vertically: flip); |
1431 | } |
1432 | |
1433 | Q_UNREACHABLE_RETURN(QImage()); |
1434 | } |
1435 | |
1436 | Q_OPENGL_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha) |
1437 | { |
1438 | return qt_gl_read_framebuffer(size, internal_format: alpha_format ? GL_RGBA : GL_RGB, include_alpha, flip: true); |
1439 | } |
1440 | |
1441 | /*! |
1442 | \fn QImage QOpenGLFramebufferObject::toImage(bool flipped) const |
1443 | |
1444 | Returns the contents of this framebuffer object as a QImage. |
1445 | |
1446 | If \a flipped is true the image is flipped from OpenGL coordinates to raster coordinates. |
1447 | If used together with QOpenGLPaintDevice, \a flipped should be the opposite of the value |
1448 | of QOpenGLPaintDevice::paintFlipped(). |
1449 | |
1450 | The returned image has a format of premultiplied ARGB32 or RGB32. The latter |
1451 | is used only when internalTextureFormat() is set to \c GL_RGB. Since Qt 5.2 |
1452 | the function will fall back to premultiplied RGBA8888 or RGBx8888 when |
1453 | reading to (A)RGB32 is not supported, and this includes OpenGL ES. Since Qt |
1454 | 5.4 an A2BGR30 image is returned if the internal format is RGB10_A2, and since |
1455 | Qt 5.12 a RGBA64 image is return if the internal format is RGBA16. |
1456 | |
1457 | If the rendering in the framebuffer was not done with premultiplied alpha in mind, |
1458 | create a wrapper QImage with a non-premultiplied format. This is necessary before |
1459 | performing operations like QImage::save() because otherwise the image data would get |
1460 | unpremultiplied, even though it was not premultiplied in the first place. To create |
1461 | such a wrapper without performing a copy of the pixel data, do the following: |
1462 | |
1463 | \code |
1464 | QImage fboImage(fbo.toImage()); |
1465 | QImage image(fboImage.constBits(), fboImage.width(), fboImage.height(), QImage::Format_ARGB32); |
1466 | \endcode |
1467 | |
1468 | For multisampled framebuffer objects the samples are resolved using the |
1469 | \c{GL_EXT_framebuffer_blit} extension. If the extension is not available, the contents |
1470 | of the returned image is undefined. |
1471 | |
1472 | For singlesampled framebuffers the contents is retrieved via \c glReadPixels. This is |
1473 | a potentially expensive and inefficient operation. Therefore it is recommended that |
1474 | this function is used as seldom as possible. |
1475 | |
1476 | \sa QOpenGLPaintDevice::paintFlipped() |
1477 | */ |
1478 | |
1479 | QImage QOpenGLFramebufferObject::toImage(bool flipped) const |
1480 | { |
1481 | return toImage(flipped, colorAttachmentIndex: 0); |
1482 | } |
1483 | |
1484 | /*! \overload |
1485 | |
1486 | Returns the contents of the color attachment of index \a |
1487 | colorAttachmentIndex of this framebuffer object as a QImage. This method |
1488 | flips the image from OpenGL coordinates to raster coordinates when \a |
1489 | flipped is set to \c true. |
1490 | |
1491 | \note This overload is only fully functional when multiple render targets are |
1492 | supported by the OpenGL implementation. When that is not the case, only one |
1493 | color attachment will be set up. |
1494 | |
1495 | \since 5.6 |
1496 | */ |
1497 | QImage QOpenGLFramebufferObject::toImage(bool flipped, int colorAttachmentIndex) const |
1498 | { |
1499 | Q_D(const QOpenGLFramebufferObject); |
1500 | if (!d->valid) |
1501 | return QImage(); |
1502 | |
1503 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
1504 | if (!ctx) { |
1505 | qWarning(msg: "QOpenGLFramebufferObject::toImage() called without a current context" ); |
1506 | return QImage(); |
1507 | } |
1508 | |
1509 | if (d->colorAttachments.size() <= colorAttachmentIndex) { |
1510 | qWarning(msg: "QOpenGLFramebufferObject::toImage() called for missing color attachment" ); |
1511 | return QImage(); |
1512 | } |
1513 | |
1514 | GLuint prevFbo = 0; |
1515 | ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, params: (GLint *) &prevFbo); |
1516 | |
1517 | if (prevFbo != d->fbo()) |
1518 | const_cast<QOpenGLFramebufferObject *>(this)->bind(); |
1519 | |
1520 | QImage image; |
1521 | QOpenGLExtraFunctions * = ctx->extraFunctions(); |
1522 | // qt_gl_read_framebuffer doesn't work on a multisample FBO |
1523 | if (format().samples() != 0) { |
1524 | QRect rect(QPoint(0, 0), size()); |
1525 | QOpenGLFramebufferObjectFormat fmt; |
1526 | if (extraFuncs->hasOpenGLFeature(feature: QOpenGLFunctions::MultipleRenderTargets)) { |
1527 | fmt.setInternalTextureFormat(d->colorAttachments[colorAttachmentIndex].internalFormat); |
1528 | QOpenGLFramebufferObject temp(d->colorAttachments[colorAttachmentIndex].size, fmt); |
1529 | blitFramebuffer(target: &temp, targetRect: rect, source: const_cast<QOpenGLFramebufferObject *>(this), sourceRect: rect, |
1530 | GL_COLOR_BUFFER_BIT, GL_NEAREST, |
1531 | readColorAttachmentIndex: colorAttachmentIndex, drawColorAttachmentIndex: 0); |
1532 | image = temp.toImage(flipped); |
1533 | } else { |
1534 | fmt.setInternalTextureFormat(d->colorAttachments[0].internalFormat); |
1535 | QOpenGLFramebufferObject temp(size(), fmt); |
1536 | blitFramebuffer(target: &temp, targetRect: rect, source: const_cast<QOpenGLFramebufferObject *>(this), sourceRect: rect); |
1537 | image = temp.toImage(flipped); |
1538 | } |
1539 | } else { |
1540 | if (extraFuncs->hasOpenGLFeature(feature: QOpenGLFunctions::MultipleRenderTargets)) { |
1541 | extraFuncs->glReadBuffer(GL_COLOR_ATTACHMENT0 + colorAttachmentIndex); |
1542 | image = qt_gl_read_framebuffer(size: d->colorAttachments[colorAttachmentIndex].size, |
1543 | internal_format: d->colorAttachments[colorAttachmentIndex].internalFormat, |
1544 | include_alpha: true, flip: flipped); |
1545 | extraFuncs->glReadBuffer(GL_COLOR_ATTACHMENT0); |
1546 | } else { |
1547 | image = qt_gl_read_framebuffer(size: d->colorAttachments[0].size, |
1548 | internal_format: d->colorAttachments[0].internalFormat, |
1549 | include_alpha: true, flip: flipped); |
1550 | } |
1551 | } |
1552 | |
1553 | if (prevFbo != d->fbo()) |
1554 | ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: prevFbo); |
1555 | |
1556 | return image; |
1557 | } |
1558 | |
1559 | /*! |
1560 | \fn bool QOpenGLFramebufferObject::bindDefault() |
1561 | |
1562 | Switches rendering back to the default, windowing system provided |
1563 | framebuffer. |
1564 | Returns \c true upon success, false otherwise. |
1565 | |
1566 | \sa bind(), release() |
1567 | */ |
1568 | bool QOpenGLFramebufferObject::bindDefault() |
1569 | { |
1570 | QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext()); |
1571 | |
1572 | if (ctx) { |
1573 | ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject()); |
1574 | QOpenGLContextPrivate::get(context: ctx)->qgl_current_fbo_invalid = true; |
1575 | QOpenGLContextPrivate::get(context: ctx)->qgl_current_fbo = nullptr; |
1576 | } |
1577 | #ifdef QT_DEBUG |
1578 | else |
1579 | qWarning(msg: "QOpenGLFramebufferObject::bindDefault() called without current context." ); |
1580 | #endif |
1581 | |
1582 | return ctx != nullptr; |
1583 | } |
1584 | |
1585 | /*! |
1586 | \fn bool QOpenGLFramebufferObject::hasOpenGLFramebufferObjects() |
1587 | |
1588 | Returns \c true if the OpenGL \c{GL_EXT_framebuffer_object} extension |
1589 | is present on this system; otherwise returns \c false. |
1590 | */ |
1591 | bool QOpenGLFramebufferObject::hasOpenGLFramebufferObjects() |
1592 | { |
1593 | return QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(feature: QOpenGLFunctions::Framebuffers); |
1594 | } |
1595 | |
1596 | /*! |
1597 | \fn GLuint QOpenGLFramebufferObject::handle() const |
1598 | |
1599 | Returns the OpenGL framebuffer object handle for this framebuffer |
1600 | object (returned by the \c{glGenFrameBuffersEXT()} function). This |
1601 | handle can be used to attach new images or buffers to the |
1602 | framebuffer. The user is responsible for cleaning up and |
1603 | destroying these objects. |
1604 | */ |
1605 | GLuint QOpenGLFramebufferObject::handle() const |
1606 | { |
1607 | Q_D(const QOpenGLFramebufferObject); |
1608 | return d->fbo(); |
1609 | } |
1610 | |
1611 | /*! |
1612 | Returns the status of the depth and stencil buffers attached to |
1613 | this framebuffer object. |
1614 | */ |
1615 | |
1616 | QOpenGLFramebufferObject::Attachment QOpenGLFramebufferObject::attachment() const |
1617 | { |
1618 | Q_D(const QOpenGLFramebufferObject); |
1619 | if (d->valid) |
1620 | return d->fbo_attachment; |
1621 | return NoAttachment; |
1622 | } |
1623 | |
1624 | /*! |
1625 | Sets the attachments of the framebuffer object to \a attachment. |
1626 | |
1627 | This can be used to free or reattach the depth and stencil buffer |
1628 | attachments as needed. |
1629 | |
1630 | \note This function alters the current framebuffer binding. |
1631 | */ |
1632 | void QOpenGLFramebufferObject::setAttachment(QOpenGLFramebufferObject::Attachment attachment) |
1633 | { |
1634 | Q_D(QOpenGLFramebufferObject); |
1635 | if (attachment == d->fbo_attachment || !isValid()) |
1636 | return; |
1637 | QOpenGLContext *current = QOpenGLContext::currentContext(); |
1638 | if (!current) |
1639 | return; |
1640 | #ifdef QT_DEBUG |
1641 | if (current->shareGroup() != d->fbo_guard->group()) |
1642 | qWarning(msg: "QOpenGLFramebufferObject::setAttachment() called from incompatible context" ); |
1643 | #endif |
1644 | d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: d->fbo()); |
1645 | QOpenGLContextPrivate::get(context: current)->qgl_current_fbo_invalid = true; |
1646 | d->initDepthStencilAttachments(ctx: current, attachment); |
1647 | } |
1648 | |
1649 | /*! |
1650 | Returns \c true if the framebuffer object is currently bound to the current context, |
1651 | otherwise false is returned. |
1652 | */ |
1653 | bool QOpenGLFramebufferObject::isBound() const |
1654 | { |
1655 | Q_D(const QOpenGLFramebufferObject); |
1656 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
1657 | if (!ctx) |
1658 | return false; |
1659 | GLint fbo = 0; |
1660 | ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, params: &fbo); |
1661 | return GLuint(fbo) == d->fbo(); |
1662 | } |
1663 | |
1664 | /*! |
1665 | \fn bool QOpenGLFramebufferObject::hasOpenGLFramebufferBlit() |
1666 | |
1667 | Returns \c true if the OpenGL \c{GL_EXT_framebuffer_blit} extension |
1668 | is present on this system; otherwise returns \c false. |
1669 | |
1670 | \sa blitFramebuffer() |
1671 | */ |
1672 | bool QOpenGLFramebufferObject::hasOpenGLFramebufferBlit() |
1673 | { |
1674 | return QOpenGLExtensions(QOpenGLContext::currentContext()).hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferBlit); |
1675 | } |
1676 | |
1677 | |
1678 | /*! |
1679 | \overload |
1680 | |
1681 | Convenience overload to blit between two framebuffer objects. |
1682 | */ |
1683 | void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, |
1684 | QOpenGLFramebufferObject *source, |
1685 | GLbitfield buffers, GLenum filter) |
1686 | { |
1687 | if (!target && !source) |
1688 | return; |
1689 | |
1690 | QSize targetSize; |
1691 | QSize sourceSize; |
1692 | |
1693 | if (target) |
1694 | targetSize = target->size(); |
1695 | if (source) |
1696 | sourceSize = source->size(); |
1697 | |
1698 | if (targetSize.isEmpty()) |
1699 | targetSize = sourceSize; |
1700 | else if (sourceSize.isEmpty()) |
1701 | sourceSize = targetSize; |
1702 | |
1703 | blitFramebuffer(target, targetRect: QRect(QPoint(0, 0), targetSize), |
1704 | source, sourceRect: QRect(QPoint(0, 0), sourceSize), |
1705 | buffers, filter); |
1706 | } |
1707 | |
1708 | /*! \overload |
1709 | * |
1710 | Convenience overload to blit between two framebuffer objects. |
1711 | */ |
1712 | void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect, |
1713 | QOpenGLFramebufferObject *source, const QRect &sourceRect, |
1714 | GLbitfield buffers, |
1715 | GLenum filter) |
1716 | { |
1717 | blitFramebuffer(target, targetRect, source, sourceRect, buffers, filter, readColorAttachmentIndex: 0, drawColorAttachmentIndex: 0); |
1718 | } |
1719 | |
1720 | /*! |
1721 | \enum QOpenGLFramebufferObject::FramebufferRestorePolicy |
1722 | \since 5.7 |
1723 | |
1724 | This enum type is used to configure the behavior related to restoring |
1725 | framebuffer bindings when calling blitFramebuffer(). |
1726 | |
1727 | \value DontRestoreFramebufferBinding Do not restore the previous framebuffer binding. |
1728 | The caller is responsible for tracking and setting |
1729 | the framebuffer binding as needed. |
1730 | |
1731 | \value RestoreFramebufferBindingToDefault After the blit operation, bind the default |
1732 | framebuffer. |
1733 | |
1734 | \value RestoreFrameBufferBinding Restore the previously bound framebuffer. This is |
1735 | potentially expensive because of the need to |
1736 | query the currently bound framebuffer. |
1737 | |
1738 | \sa blitFramebuffer() |
1739 | */ |
1740 | |
1741 | /*! |
1742 | \since 5.7 |
1743 | |
1744 | Blits from the \a sourceRect rectangle in the \a source framebuffer |
1745 | object to the \a targetRect rectangle in the \a target framebuffer object. |
1746 | |
1747 | If \a source or \a target is 0, the default framebuffer will be used |
1748 | instead of a framebuffer object as source or target respectively. |
1749 | |
1750 | This function will have no effect unless hasOpenGLFramebufferBlit() returns |
1751 | true. |
1752 | |
1753 | The \a buffers parameter should be a mask consisting of any combination of |
1754 | \c GL_COLOR_BUFFER_BIT, \c GL_DEPTH_BUFFER_BIT, and |
1755 | \c GL_STENCIL_BUFFER_BIT. Any buffer type that is not present both |
1756 | in the source and target buffers is ignored. |
1757 | |
1758 | The \a sourceRect and \a targetRect rectangles may have different sizes; |
1759 | in this case \a buffers should not contain \c GL_DEPTH_BUFFER_BIT or |
1760 | \c GL_STENCIL_BUFFER_BIT. The \a filter parameter should be set to |
1761 | \c GL_LINEAR or \c GL_NEAREST, and specifies whether linear or nearest |
1762 | interpolation should be used when scaling is performed. |
1763 | |
1764 | If \a source equals \a target a copy is performed within the same buffer. |
1765 | Results are undefined if the source and target rectangles overlap and |
1766 | have different sizes. The sizes must also be the same if any of the |
1767 | framebuffer objects are multisample framebuffers. |
1768 | |
1769 | \note The scissor test will restrict the blit area if enabled. |
1770 | |
1771 | When multiple render targets are in use, \a readColorAttachmentIndex and \a |
1772 | drawColorAttachmentIndex specify the index of the color attachments in the |
1773 | source and destination framebuffers. |
1774 | |
1775 | The \a restorePolicy determines if the framebuffer that was bound prior to |
1776 | calling this function should be restored, or if the default framebuffer |
1777 | should be bound before returning, of if the caller is responsible for |
1778 | tracking and setting the bound framebuffer. Restoring the previous |
1779 | framebuffer can be relatively expensive due to the call to \c{glGetIntegerv} |
1780 | which on some OpenGL drivers may imply a pipeline stall. |
1781 | |
1782 | \sa hasOpenGLFramebufferBlit() |
1783 | */ |
1784 | void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect, |
1785 | QOpenGLFramebufferObject *source, const QRect &sourceRect, |
1786 | GLbitfield buffers, |
1787 | GLenum filter, |
1788 | int readColorAttachmentIndex, |
1789 | int drawColorAttachmentIndex, |
1790 | QOpenGLFramebufferObject::FramebufferRestorePolicy restorePolicy) |
1791 | { |
1792 | QOpenGLContext *ctx = QOpenGLContext::currentContext(); |
1793 | if (!ctx) |
1794 | return; |
1795 | |
1796 | QOpenGLExtensions extensions(ctx); |
1797 | if (!extensions.hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferBlit)) |
1798 | return; |
1799 | |
1800 | GLuint prevFbo = 0; |
1801 | if (restorePolicy == RestoreFrameBufferBinding) |
1802 | ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, params: (GLint *) &prevFbo); |
1803 | |
1804 | const int sx0 = sourceRect.left(); |
1805 | const int sx1 = sourceRect.left() + sourceRect.width(); |
1806 | const int sy0 = sourceRect.top(); |
1807 | const int sy1 = sourceRect.top() + sourceRect.height(); |
1808 | |
1809 | const int tx0 = targetRect.left(); |
1810 | const int tx1 = targetRect.left() + targetRect.width(); |
1811 | const int ty0 = targetRect.top(); |
1812 | const int ty1 = targetRect.top() + targetRect.height(); |
1813 | |
1814 | const GLuint defaultFboId = ctx->defaultFramebufferObject(); |
1815 | |
1816 | extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer: source ? source->handle() : defaultFboId); |
1817 | extensions.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer: target ? target->handle() : defaultFboId); |
1818 | |
1819 | const bool supportsMRT = extensions.hasOpenGLFeature(feature: QOpenGLFunctions::MultipleRenderTargets); |
1820 | if (supportsMRT) { |
1821 | extensions.glReadBuffer(GL_COLOR_ATTACHMENT0 + readColorAttachmentIndex); |
1822 | if (target) { |
1823 | GLenum drawBuf = GL_COLOR_ATTACHMENT0 + drawColorAttachmentIndex; |
1824 | extensions.glDrawBuffers(n: 1, bufs: &drawBuf); |
1825 | } |
1826 | } |
1827 | |
1828 | extensions.glBlitFramebuffer(srcX0: sx0, srcY0: sy0, srcX1: sx1, srcY1: sy1, |
1829 | dstX0: tx0, dstY0: ty0, dstX1: tx1, dstY1: ty1, |
1830 | mask: buffers, filter); |
1831 | |
1832 | if (supportsMRT) |
1833 | extensions.glReadBuffer(GL_COLOR_ATTACHMENT0); |
1834 | |
1835 | switch (restorePolicy) { |
1836 | case RestoreFrameBufferBinding: |
1837 | ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: prevFbo); // sets both READ and DRAW |
1838 | break; |
1839 | |
1840 | case RestoreFramebufferBindingToDefault: |
1841 | ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject()); // sets both READ and DRAW |
1842 | break; |
1843 | |
1844 | case DontRestoreFramebufferBinding: |
1845 | break; |
1846 | } |
1847 | } |
1848 | |
1849 | /*! |
1850 | \overload |
1851 | |
1852 | Convenience overload to blit between two framebuffer objects and |
1853 | to restore the previous framebuffer binding. Equivalent to calling |
1854 | blitFramebuffer(target, targetRect, source, sourceRect, buffers, filter, |
1855 | readColorAttachmentIndex, drawColorAttachmentIndex, |
1856 | RestoreFrameBufferBinding). |
1857 | */ |
1858 | void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect, |
1859 | QOpenGLFramebufferObject *source, const QRect &sourceRect, |
1860 | GLbitfield buffers, |
1861 | GLenum filter, |
1862 | int readColorAttachmentIndex, |
1863 | int drawColorAttachmentIndex) |
1864 | { |
1865 | blitFramebuffer(target, targetRect, source, sourceRect, |
1866 | buffers, filter, |
1867 | readColorAttachmentIndex, |
1868 | drawColorAttachmentIndex, |
1869 | restorePolicy: RestoreFrameBufferBinding); |
1870 | } |
1871 | |
1872 | QT_END_NAMESPACE |
1873 | |