Support MRT in QOpenGLFramebufferObject
Introduce overloads in the API to allow specifying multiple color attachment sizes and formats. When these are in use and MRT is supported, a texture or renderbuffer is created for each of GL_COLOR_ATTACHMENT0, 1, 2, ... [ChangeLog] Added support for multiple render targets in QOpenGLFramebufferObject Task-number: QTBUG-39235 Change-Id: Ie7cfd81d1b796a9166b80dff7513aafe0120d53d Reviewed-by: Jørgen Lind <jorgen.lind@theqtcompany.com>
This commit is contained in:
parent
c173a50719
commit
06b86f68e8
@ -166,7 +166,7 @@ void QOpenGLFramebufferObjectFormat::detach()
|
||||
the format of an OpenGL framebuffer object.
|
||||
|
||||
By default the format specifies a non-multisample framebuffer object with no
|
||||
attachments, texture target \c GL_TEXTURE_2D, and internal format \c GL_RGBA8.
|
||||
depth/stencil attachments, texture target \c GL_TEXTURE_2D, and internal format \c GL_RGBA8.
|
||||
On OpenGL/ES systems, the default internal format is \c GL_RGBA.
|
||||
|
||||
\sa samples(), attachment(), internalTextureFormat()
|
||||
@ -436,10 +436,10 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSize &sz,
|
||||
QOpenGLFramebufferObject::Attachment attachment,
|
||||
GLenum texture_target, GLenum internal_format,
|
||||
GLint samples, bool mipmap)
|
||||
void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSize &size,
|
||||
QOpenGLFramebufferObject::Attachment attachment,
|
||||
GLenum texture_target, GLenum internal_format,
|
||||
GLint samples, bool mipmap)
|
||||
{
|
||||
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
||||
|
||||
@ -458,9 +458,13 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi
|
||||
samples = qBound(0, int(samples), int(maxSamples));
|
||||
}
|
||||
|
||||
colorAttachments.append(ColorAttachment(size, internal_format));
|
||||
|
||||
dsSize = size;
|
||||
|
||||
samples = qMax(0, samples);
|
||||
requestedSamples = samples;
|
||||
size = sz;
|
||||
|
||||
target = texture_target;
|
||||
|
||||
QT_RESET_GLERROR(); // reset error state
|
||||
@ -471,64 +475,30 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi
|
||||
|
||||
QOpenGLContextPrivate::get(ctx)->qgl_current_fbo_invalid = true;
|
||||
|
||||
GLuint color_buffer = 0;
|
||||
|
||||
QT_CHECK_GLERROR();
|
||||
// init texture
|
||||
if (samples == 0) {
|
||||
initTexture(texture_target, internal_format, size, mipmap);
|
||||
} else {
|
||||
GLenum storageFormat = internal_format;
|
||||
// ES requires a sized format. The older desktop extension does not. Correct the format on ES.
|
||||
if (ctx->isOpenGLES() && internal_format == GL_RGBA) {
|
||||
if (funcs.hasOpenGLExtension(QOpenGLExtensions::Sized8Formats))
|
||||
storageFormat = GL_RGBA8;
|
||||
else
|
||||
storageFormat = GL_RGBA4;
|
||||
}
|
||||
|
||||
mipmap = false;
|
||||
funcs.glGenRenderbuffers(1, &color_buffer);
|
||||
funcs.glBindRenderbuffer(GL_RENDERBUFFER, color_buffer);
|
||||
funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, storageFormat, size.width(), size.height());
|
||||
funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
GL_RENDERBUFFER, color_buffer);
|
||||
QT_CHECK_GLERROR();
|
||||
valid = checkFramebufferStatus(ctx);
|
||||
|
||||
if (valid) {
|
||||
// Query the actual number of samples. This can be greater than the requested
|
||||
// value since the typically supported values are 0, 4, 8, ..., and the
|
||||
// requests are mapped to the next supported value.
|
||||
funcs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples);
|
||||
color_buffer_guard = new QOpenGLSharedResourceGuard(ctx, color_buffer, freeRenderbufferFunc);
|
||||
}
|
||||
}
|
||||
|
||||
format.setTextureTarget(target);
|
||||
format.setSamples(int(samples));
|
||||
format.setInternalTextureFormat(internal_format);
|
||||
format.setMipmap(mipmap);
|
||||
|
||||
initAttachments(ctx, attachment);
|
||||
if (samples == 0)
|
||||
initTexture(0);
|
||||
else
|
||||
initColorBuffer(0, &samples);
|
||||
|
||||
if (valid) {
|
||||
format.setSamples(int(samples));
|
||||
|
||||
initDepthStencilAttachments(ctx, attachment);
|
||||
|
||||
if (valid)
|
||||
fbo_guard = new QOpenGLSharedResourceGuard(ctx, fbo, freeFramebufferFunc);
|
||||
} else {
|
||||
if (color_buffer_guard) {
|
||||
color_buffer_guard->free();
|
||||
color_buffer_guard = 0;
|
||||
} else if (texture_guard) {
|
||||
texture_guard->free();
|
||||
texture_guard = 0;
|
||||
}
|
||||
else
|
||||
funcs.glDeleteFramebuffers(1, &fbo);
|
||||
}
|
||||
|
||||
QT_CHECK_GLERROR();
|
||||
}
|
||||
|
||||
void QOpenGLFramebufferObjectPrivate::initTexture(GLenum target, GLenum internal_format,
|
||||
const QSize &size, bool mipmap)
|
||||
void QOpenGLFramebufferObjectPrivate::initTexture(int idx)
|
||||
{
|
||||
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
||||
GLuint texture = 0;
|
||||
@ -541,37 +511,76 @@ void QOpenGLFramebufferObjectPrivate::initTexture(GLenum target, GLenum internal
|
||||
funcs.glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
funcs.glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
ColorAttachment &color(colorAttachments[idx]);
|
||||
|
||||
GLuint pixelType = GL_UNSIGNED_BYTE;
|
||||
if (internal_format == GL_RGB10_A2 || internal_format == GL_RGB10)
|
||||
if (color.internalFormat == GL_RGB10_A2 || color.internalFormat == GL_RGB10)
|
||||
pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
|
||||
funcs.glTexImage2D(target, 0, internal_format, size.width(), size.height(), 0,
|
||||
funcs.glTexImage2D(target, 0, color.internalFormat, color.size.width(), color.size.height(), 0,
|
||||
GL_RGBA, pixelType, NULL);
|
||||
if (mipmap) {
|
||||
int width = size.width();
|
||||
int height = size.height();
|
||||
if (format.mipmap()) {
|
||||
int width = color.size.width();
|
||||
int height = color.size.height();
|
||||
int level = 0;
|
||||
while (width > 1 || height > 1) {
|
||||
width = qMax(1, width >> 1);
|
||||
height = qMax(1, height >> 1);
|
||||
++level;
|
||||
funcs.glTexImage2D(target, level, internal_format, width, height, 0,
|
||||
funcs.glTexImage2D(target, level, color.internalFormat, width, height, 0,
|
||||
GL_RGBA, pixelType, NULL);
|
||||
}
|
||||
}
|
||||
funcs.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
funcs.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + idx,
|
||||
target, texture, 0);
|
||||
|
||||
QT_CHECK_GLERROR();
|
||||
funcs.glBindTexture(target, 0);
|
||||
valid = checkFramebufferStatus(ctx);
|
||||
if (valid)
|
||||
texture_guard = new QOpenGLSharedResourceGuard(ctx, texture, freeTextureFunc);
|
||||
else
|
||||
if (valid) {
|
||||
color.guard = new QOpenGLSharedResourceGuard(ctx, texture, freeTextureFunc);
|
||||
} else {
|
||||
funcs.glDeleteTextures(1, &texture);
|
||||
}
|
||||
}
|
||||
|
||||
void QOpenGLFramebufferObjectPrivate::initAttachments(QOpenGLContext *ctx, QOpenGLFramebufferObject::Attachment attachment)
|
||||
void QOpenGLFramebufferObjectPrivate::initColorBuffer(int idx, GLint *samples)
|
||||
{
|
||||
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
||||
GLuint color_buffer = 0;
|
||||
|
||||
ColorAttachment &color(colorAttachments[idx]);
|
||||
|
||||
GLenum storageFormat = color.internalFormat;
|
||||
// ES requires a sized format. The older desktop extension does not. Correct the format on ES.
|
||||
if (ctx->isOpenGLES() && color.internalFormat == GL_RGBA) {
|
||||
if (funcs.hasOpenGLExtension(QOpenGLExtensions::Sized8Formats))
|
||||
storageFormat = GL_RGBA8;
|
||||
else
|
||||
storageFormat = GL_RGBA4;
|
||||
}
|
||||
|
||||
funcs.glGenRenderbuffers(1, &color_buffer);
|
||||
funcs.glBindRenderbuffer(GL_RENDERBUFFER, color_buffer);
|
||||
funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, *samples, storageFormat, color.size.width(), color.size.height());
|
||||
funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + idx,
|
||||
GL_RENDERBUFFER, color_buffer);
|
||||
|
||||
QT_CHECK_GLERROR();
|
||||
valid = checkFramebufferStatus(ctx);
|
||||
if (valid) {
|
||||
// Query the actual number of samples. This can be greater than the requested
|
||||
// value since the typically supported values are 0, 4, 8, ..., and the
|
||||
// requests are mapped to the next supported value.
|
||||
funcs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, samples);
|
||||
color.guard = new QOpenGLSharedResourceGuard(ctx, color_buffer, freeRenderbufferFunc);
|
||||
} else {
|
||||
funcs.glDeleteRenderbuffers(1, &color_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void QOpenGLFramebufferObjectPrivate::initDepthStencilAttachments(QOpenGLContext *ctx,
|
||||
QOpenGLFramebufferObject::Attachment attachment)
|
||||
{
|
||||
// Use the same sample count for all attachments. format.samples() already contains
|
||||
// the actual number of samples for the color attachment and is not suitable. Use
|
||||
@ -608,10 +617,10 @@ void QOpenGLFramebufferObjectPrivate::initAttachments(QOpenGLContext *ctx, QOpen
|
||||
Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer));
|
||||
if (samples != 0 && funcs.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample))
|
||||
funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
|
||||
GL_DEPTH24_STENCIL8, size.width(), size.height());
|
||||
GL_DEPTH24_STENCIL8, dsSize.width(), dsSize.height());
|
||||
else
|
||||
funcs.glRenderbufferStorage(GL_RENDERBUFFER,
|
||||
GL_DEPTH24_STENCIL8, size.width(), size.height());
|
||||
GL_DEPTH24_STENCIL8, dsSize.width(), dsSize.height());
|
||||
|
||||
stencil_buffer = depth_buffer;
|
||||
funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
||||
@ -636,25 +645,25 @@ void QOpenGLFramebufferObjectPrivate::initAttachments(QOpenGLContext *ctx, QOpen
|
||||
if (ctx->isOpenGLES()) {
|
||||
if (funcs.hasOpenGLExtension(QOpenGLExtensions::Depth24))
|
||||
funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
|
||||
GL_DEPTH_COMPONENT24, size.width(), size.height());
|
||||
GL_DEPTH_COMPONENT24, dsSize.width(), dsSize.height());
|
||||
else
|
||||
funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
|
||||
GL_DEPTH_COMPONENT16, size.width(), size.height());
|
||||
GL_DEPTH_COMPONENT16, dsSize.width(), dsSize.height());
|
||||
} else {
|
||||
funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
|
||||
GL_DEPTH_COMPONENT, size.width(), size.height());
|
||||
GL_DEPTH_COMPONENT, dsSize.width(), dsSize.height());
|
||||
}
|
||||
} else {
|
||||
if (ctx->isOpenGLES()) {
|
||||
if (funcs.hasOpenGLExtension(QOpenGLExtensions::Depth24)) {
|
||||
funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24,
|
||||
size.width(), size.height());
|
||||
dsSize.width(), dsSize.height());
|
||||
} else {
|
||||
funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
|
||||
size.width(), size.height());
|
||||
dsSize.width(), dsSize.height());
|
||||
}
|
||||
} else {
|
||||
funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.width(), size.height());
|
||||
funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, dsSize.width(), dsSize.height());
|
||||
}
|
||||
}
|
||||
funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
||||
@ -678,9 +687,9 @@ void QOpenGLFramebufferObjectPrivate::initAttachments(QOpenGLContext *ctx, QOpen
|
||||
#endif
|
||||
|
||||
if (samples != 0 && funcs.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample))
|
||||
funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, storage, size.width(), size.height());
|
||||
funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, storage, dsSize.width(), dsSize.height());
|
||||
else
|
||||
funcs.glRenderbufferStorage(GL_RENDERBUFFER, storage, size.width(), size.height());
|
||||
funcs.glRenderbufferStorage(GL_RENDERBUFFER, storage, dsSize.width(), dsSize.height());
|
||||
|
||||
funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
|
||||
GL_RENDERBUFFER, stencil_buffer);
|
||||
@ -756,6 +765,11 @@ void QOpenGLFramebufferObjectPrivate::initAttachments(QOpenGLContext *ctx, QOpen
|
||||
format, and will be bound to the \c GL_COLOR_ATTACHMENT0
|
||||
attachment in the framebuffer object.
|
||||
|
||||
Multiple render targets are also supported, in case the OpenGL
|
||||
implementation supports this. Here there will be multiple textures (or, in
|
||||
case of multisampling, renderbuffers) present and each of them will get
|
||||
attached to \c GL_COLOR_ATTACHMENT0, \c 1, \c 2, ...
|
||||
|
||||
If you want to use a framebuffer object with multisampling enabled
|
||||
as a texture, you first need to copy from it to a regular framebuffer
|
||||
object using QOpenGLContext::blitFramebuffer().
|
||||
@ -785,6 +799,16 @@ void QOpenGLFramebufferObjectPrivate::initAttachments(QOpenGLContext *ctx, QOpen
|
||||
\sa attachment()
|
||||
*/
|
||||
|
||||
static inline GLenum effectiveInternalFormat(GLenum internalFormat)
|
||||
{
|
||||
if (!internalFormat)
|
||||
#ifdef QT_OPENGL_ES_2
|
||||
internalFormat = GL_RGBA;
|
||||
#else
|
||||
internalFormat = QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8;
|
||||
#endif
|
||||
return internalFormat;
|
||||
}
|
||||
|
||||
/*! \fn QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, GLenum target)
|
||||
|
||||
@ -814,13 +838,7 @@ QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, GLenum tar
|
||||
: d_ptr(new QOpenGLFramebufferObjectPrivate)
|
||||
{
|
||||
Q_D(QOpenGLFramebufferObject);
|
||||
d->init(this, size, NoAttachment, target,
|
||||
#ifndef QT_OPENGL_ES_2
|
||||
QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8
|
||||
#else
|
||||
GL_RGBA
|
||||
#endif
|
||||
);
|
||||
d->init(this, size, NoAttachment, target, effectiveInternalFormat(0));
|
||||
}
|
||||
|
||||
/*! \overload
|
||||
@ -834,13 +852,7 @@ QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, GLenum
|
||||
: d_ptr(new QOpenGLFramebufferObjectPrivate)
|
||||
{
|
||||
Q_D(QOpenGLFramebufferObject);
|
||||
d->init(this, QSize(width, height), NoAttachment, target,
|
||||
#ifndef QT_OPENGL_ES_2
|
||||
QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8
|
||||
#else
|
||||
GL_RGBA
|
||||
#endif
|
||||
);
|
||||
d->init(this, QSize(width, height), NoAttachment, target, effectiveInternalFormat(0));
|
||||
}
|
||||
|
||||
/*! \overload
|
||||
@ -877,7 +889,7 @@ QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, const
|
||||
buffer of the given \a width and \a height.
|
||||
|
||||
The \a attachment parameter describes the depth/stencil buffer
|
||||
configuration, \a target the texture target and \a internal_format
|
||||
configuration, \a target the texture target and \a internalFormat
|
||||
the internal texture format. The default texture target is \c
|
||||
GL_TEXTURE_2D, while the default internal format is \c GL_RGBA8
|
||||
for desktop OpenGL and \c GL_RGBA for OpenGL/ES.
|
||||
@ -885,17 +897,11 @@ QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, const
|
||||
\sa size(), texture(), attachment()
|
||||
*/
|
||||
QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, Attachment attachment,
|
||||
GLenum target, GLenum internal_format)
|
||||
GLenum target, GLenum internalFormat)
|
||||
: d_ptr(new QOpenGLFramebufferObjectPrivate)
|
||||
{
|
||||
Q_D(QOpenGLFramebufferObject);
|
||||
if (!internal_format)
|
||||
#ifdef QT_OPENGL_ES_2
|
||||
internal_format = GL_RGBA;
|
||||
#else
|
||||
internal_format = QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8;
|
||||
#endif
|
||||
d->init(this, QSize(width, height), attachment, target, internal_format);
|
||||
d->init(this, QSize(width, height), attachment, target, effectiveInternalFormat(internalFormat));
|
||||
}
|
||||
|
||||
/*! \overload
|
||||
@ -904,7 +910,7 @@ QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, Attach
|
||||
buffer of the given \a size.
|
||||
|
||||
The \a attachment parameter describes the depth/stencil buffer
|
||||
configuration, \a target the texture target and \a internal_format
|
||||
configuration, \a target the texture target and \a internalFormat
|
||||
the internal texture format. The default texture target is \c
|
||||
GL_TEXTURE_2D, while the default internal format is \c GL_RGBA8
|
||||
for desktop OpenGL and \c GL_RGBA for OpenGL/ES.
|
||||
@ -912,17 +918,11 @@ QOpenGLFramebufferObject::QOpenGLFramebufferObject(int width, int height, Attach
|
||||
\sa size(), texture(), attachment()
|
||||
*/
|
||||
QOpenGLFramebufferObject::QOpenGLFramebufferObject(const QSize &size, Attachment attachment,
|
||||
GLenum target, GLenum internal_format)
|
||||
GLenum target, GLenum internalFormat)
|
||||
: d_ptr(new QOpenGLFramebufferObjectPrivate)
|
||||
{
|
||||
Q_D(QOpenGLFramebufferObject);
|
||||
if (!internal_format)
|
||||
#ifdef QT_OPENGL_ES_2
|
||||
internal_format = GL_RGBA;
|
||||
#else
|
||||
internal_format = QOpenGLContext::currentContext()->isOpenGLES() ? GL_RGBA : GL_RGBA8;
|
||||
#endif
|
||||
d->init(this, size, attachment, target, internal_format);
|
||||
d->init(this, size, attachment, target, effectiveInternalFormat(internalFormat));
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -936,10 +936,12 @@ QOpenGLFramebufferObject::~QOpenGLFramebufferObject()
|
||||
if (isBound())
|
||||
release();
|
||||
|
||||
if (d->texture_guard)
|
||||
d->texture_guard->free();
|
||||
if (d->color_buffer_guard)
|
||||
d->color_buffer_guard->free();
|
||||
foreach (const QOpenGLFramebufferObjectPrivate::ColorAttachment &color, d->colorAttachments) {
|
||||
if (color.guard)
|
||||
color.guard->free();
|
||||
}
|
||||
d->colorAttachments.clear();
|
||||
|
||||
if (d->depth_buffer_guard)
|
||||
d->depth_buffer_guard->free();
|
||||
if (d->stencil_buffer_guard && d->stencil_buffer_guard != d->depth_buffer_guard)
|
||||
@ -948,6 +950,70 @@ QOpenGLFramebufferObject::~QOpenGLFramebufferObject()
|
||||
d->fbo_guard->free();
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and attaches an additional texture or renderbuffer of size \a width
|
||||
and \a height.
|
||||
|
||||
There is always an attachment at GL_COLOR_ATTACHMENT0. Call this function
|
||||
to set up additional attachments at GL_COLOR_ATTACHMENT1,
|
||||
GL_COLOR_ATTACHMENT2, ...
|
||||
|
||||
When \a internalFormat is not \c 0, it specifies the internal format of the
|
||||
texture or renderbuffer. Otherwise a default of GL_RGBA or GL_RGBA8 is
|
||||
used.
|
||||
|
||||
\note This is only functional when multiple render targets are supported by
|
||||
the OpenGL implementation. When that is not the case, the function will not
|
||||
add any additional color attachments. Call
|
||||
QOpenGLFunctions::hasOpenGLFeature() with
|
||||
QOpenGLFunctions::MultipleRenderTargets at runtime to check if MRT is
|
||||
supported.
|
||||
|
||||
\note The internal format of the color attachments may differ but there may
|
||||
be limitations on the supported combinations, depending on the drivers.
|
||||
|
||||
\note The size of the color attachments may differ but rendering is limited
|
||||
to the area that fits all the attachments, according to the OpenGL
|
||||
specification. Some drivers may not be fully conformant in this respect,
|
||||
however.
|
||||
|
||||
\since 5.6
|
||||
*/
|
||||
void QOpenGLFramebufferObject::addColorAttachment(const QSize &size, GLenum internalFormat)
|
||||
{
|
||||
Q_D(QOpenGLFramebufferObject);
|
||||
|
||||
if (!QOpenGLContext::currentContext()->functions()->hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets)) {
|
||||
qWarning("Multiple render targets not supported, ignoring extra color attachment request");
|
||||
return;
|
||||
}
|
||||
|
||||
QOpenGLFramebufferObjectPrivate::ColorAttachment color(size, effectiveInternalFormat(internalFormat));
|
||||
d->colorAttachments.append(color);
|
||||
const int idx = d->colorAttachments.count() - 1;
|
||||
|
||||
if (d->requestedSamples == 0) {
|
||||
d->initTexture(idx);
|
||||
} else {
|
||||
GLint samples = d->requestedSamples;
|
||||
d->initColorBuffer(idx, &samples);
|
||||
}
|
||||
}
|
||||
|
||||
/*! \overload
|
||||
|
||||
Creates and attaches an additional texture or renderbuffer of size \a width and \a height.
|
||||
|
||||
When \a internalFormat is not \c 0, it specifies the internal format of the texture or
|
||||
renderbuffer. Otherwise a default of GL_RGBA or GL_RGBA8 is used.
|
||||
|
||||
\since 5.6
|
||||
*/
|
||||
void QOpenGLFramebufferObject::addColorAttachment(int width, int height, GLenum internalFormat)
|
||||
{
|
||||
addColorAttachment(QSize(width, height), internalFormat);
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn bool QOpenGLFramebufferObject::isValid() const
|
||||
|
||||
@ -1002,10 +1068,16 @@ bool QOpenGLFramebufferObject::bind()
|
||||
|
||||
QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true;
|
||||
|
||||
if (d->texture_guard || d->format.samples() != 0)
|
||||
d->valid = d->checkFramebufferStatus(current);
|
||||
else
|
||||
d->initTexture(d->format.textureTarget(), d->format.internalTextureFormat(), d->size, d->format.mipmap());
|
||||
if (d->format.samples() == 0) {
|
||||
// Create new textures to replace the ones stolen via takeTexture().
|
||||
for (int i = 0; i < d->colorAttachments.count(); ++i) {
|
||||
if (!d->colorAttachments[i].guard)
|
||||
d->initTexture(i);
|
||||
}
|
||||
}
|
||||
|
||||
d->valid = d->checkFramebufferStatus(current);
|
||||
|
||||
return d->valid;
|
||||
}
|
||||
|
||||
@ -1052,12 +1124,36 @@ bool QOpenGLFramebufferObject::release()
|
||||
If a multisample framebuffer object is used then the value returned
|
||||
from this function will be invalid.
|
||||
|
||||
\sa takeTexture()
|
||||
When multiple textures are attached, the return value is the ID of
|
||||
the first one.
|
||||
|
||||
\sa takeTexture(), textures()
|
||||
*/
|
||||
GLuint QOpenGLFramebufferObject::texture() const
|
||||
{
|
||||
Q_D(const QOpenGLFramebufferObject);
|
||||
return d->texture_guard ? d->texture_guard->id() : 0;
|
||||
return d->colorAttachments[0].guard ? d->colorAttachments[0].guard->id() : 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the texture id for all attached textures.
|
||||
|
||||
If a multisample framebuffer object is used, then an empty vector is returned.
|
||||
|
||||
\since 5.6
|
||||
|
||||
\sa takeTextures(), texture()
|
||||
*/
|
||||
QVector<GLuint> QOpenGLFramebufferObject::textures() const
|
||||
{
|
||||
Q_D(const QOpenGLFramebufferObject);
|
||||
QVector<GLuint> ids;
|
||||
if (d->format.samples() != 0)
|
||||
return ids;
|
||||
ids.reserve(d->colorAttachments.count());
|
||||
foreach (const QOpenGLFramebufferObjectPrivate::ColorAttachment &color, d->colorAttachments)
|
||||
ids.append(color.guard ? color.guard->id() : 0);
|
||||
return ids;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1076,34 +1172,72 @@ GLuint QOpenGLFramebufferObject::texture() const
|
||||
|
||||
\since 5.3
|
||||
|
||||
\sa texture(), bind(), release()
|
||||
\sa texture(), bind(), release(), takeTextures()
|
||||
*/
|
||||
GLuint QOpenGLFramebufferObject::takeTexture()
|
||||
{
|
||||
return takeTexture(0);
|
||||
}
|
||||
|
||||
/*! \overload
|
||||
|
||||
Returns the texture id for the texture attached to the color attachment of
|
||||
index \a colorAttachmentIndex of this framebuffer object. The ownership of
|
||||
the texture is transferred to the caller.
|
||||
|
||||
When \a colorAttachmentIndex is \c 0, the behavior is identical to the
|
||||
parameter-less variant of this function.
|
||||
|
||||
If the framebuffer object is currently bound, an implicit release()
|
||||
will be done. During the next call to bind() a new texture will be
|
||||
created.
|
||||
|
||||
If a multisample framebuffer object is used, then there is no
|
||||
texture and the return value from this function will be invalid.
|
||||
Similarly, incomplete framebuffer objects will also return 0.
|
||||
|
||||
\since 5.6
|
||||
*/
|
||||
GLuint QOpenGLFramebufferObject::takeTexture(int colorAttachmentIndex)
|
||||
{
|
||||
Q_D(QOpenGLFramebufferObject);
|
||||
GLuint id = 0;
|
||||
if (isValid() && d->texture_guard) {
|
||||
if (isValid() && d->format.samples() == 0 && d->colorAttachments.count() > colorAttachmentIndex) {
|
||||
QOpenGLContext *current = QOpenGLContext::currentContext();
|
||||
if (current && current->shareGroup() == d->fbo_guard->group() && isBound())
|
||||
release();
|
||||
id = d->texture_guard->id();
|
||||
id = d->colorAttachments[colorAttachmentIndex].guard ? d->colorAttachments[colorAttachmentIndex].guard->id() : 0;
|
||||
// Do not call free() on texture_guard, just null it out.
|
||||
// This way the texture will not be deleted when the guard is destroyed.
|
||||
d->texture_guard = 0;
|
||||
d->colorAttachments[colorAttachmentIndex].guard = 0;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QSize QOpenGLFramebufferObject::size() const
|
||||
|
||||
Returns the size of the texture attached to this framebuffer
|
||||
object.
|
||||
\return the size of the color and depth/stencil attachments attached to
|
||||
this framebuffer object.
|
||||
*/
|
||||
QSize QOpenGLFramebufferObject::size() const
|
||||
{
|
||||
Q_D(const QOpenGLFramebufferObject);
|
||||
return d->size;
|
||||
return d->dsSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
\return the sizes of all color attachments attached to this framebuffer
|
||||
object.
|
||||
|
||||
\since 5.6
|
||||
*/
|
||||
QVector<QSize> QOpenGLFramebufferObject::sizes() const
|
||||
{
|
||||
Q_D(const QOpenGLFramebufferObject);
|
||||
QVector<QSize> sz;
|
||||
sz.reserve(d->colorAttachments.size());
|
||||
foreach (const QOpenGLFramebufferObjectPrivate::ColorAttachment &color, d->colorAttachments)
|
||||
sz.append(color.size);
|
||||
return sz;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1232,39 +1366,7 @@ Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format,
|
||||
|
||||
QImage QOpenGLFramebufferObject::toImage(bool flipped) const
|
||||
{
|
||||
Q_D(const QOpenGLFramebufferObject);
|
||||
if (!d->valid)
|
||||
return QImage();
|
||||
|
||||
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
||||
if (!ctx) {
|
||||
qWarning("QOpenGLFramebufferObject::toImage() called without a current context");
|
||||
return QImage();
|
||||
}
|
||||
|
||||
GLuint prevFbo = 0;
|
||||
ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &prevFbo);
|
||||
|
||||
if (prevFbo != d->fbo())
|
||||
const_cast<QOpenGLFramebufferObject *>(this)->bind();
|
||||
|
||||
QImage image;
|
||||
// qt_gl_read_framebuffer doesn't work on a multisample FBO
|
||||
if (format().samples() != 0) {
|
||||
QOpenGLFramebufferObject temp(size(), QOpenGLFramebufferObjectFormat());
|
||||
|
||||
QRect rect(QPoint(0, 0), size());
|
||||
blitFramebuffer(&temp, rect, const_cast<QOpenGLFramebufferObject *>(this), rect);
|
||||
|
||||
image = temp.toImage(flipped);
|
||||
} else {
|
||||
image = qt_gl_read_framebuffer(d->size, format().internalTextureFormat(), true, flipped);
|
||||
}
|
||||
|
||||
if (prevFbo != d->fbo())
|
||||
ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, prevFbo);
|
||||
|
||||
return image;
|
||||
return toImage(flipped, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1277,7 +1379,79 @@ QImage QOpenGLFramebufferObject::toImage(bool flipped) const
|
||||
// ### Qt 6: Remove this method and make it a default argument instead.
|
||||
QImage QOpenGLFramebufferObject::toImage() const
|
||||
{
|
||||
return toImage(true);
|
||||
return toImage(true, 0);
|
||||
}
|
||||
|
||||
/*! \overload
|
||||
|
||||
Returns the contents of the color attachment of index \a
|
||||
colorAttachmentIndex of this framebuffer object as a QImage. This method
|
||||
flips the image from OpenGL coordinates to raster coordinates when \a
|
||||
flipped is set to \c true.
|
||||
|
||||
\note This overload is only fully functional when multiple render targets are
|
||||
supported by the OpenGL implementation. When that is not the case, only one
|
||||
color attachment will be set up.
|
||||
|
||||
\since 5.6
|
||||
*/
|
||||
QImage QOpenGLFramebufferObject::toImage(bool flipped, int colorAttachmentIndex) const
|
||||
{
|
||||
Q_D(const QOpenGLFramebufferObject);
|
||||
if (!d->valid)
|
||||
return QImage();
|
||||
|
||||
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
||||
if (!ctx) {
|
||||
qWarning("QOpenGLFramebufferObject::toImage() called without a current context");
|
||||
return QImage();
|
||||
}
|
||||
|
||||
if (d->colorAttachments.count() <= colorAttachmentIndex) {
|
||||
qWarning("QOpenGLFramebufferObject::toImage() called for missing color attachment");
|
||||
return QImage();
|
||||
}
|
||||
|
||||
GLuint prevFbo = 0;
|
||||
ctx->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &prevFbo);
|
||||
|
||||
if (prevFbo != d->fbo())
|
||||
const_cast<QOpenGLFramebufferObject *>(this)->bind();
|
||||
|
||||
QImage image;
|
||||
QOpenGLExtraFunctions *extraFuncs = ctx->extraFunctions();
|
||||
// qt_gl_read_framebuffer doesn't work on a multisample FBO
|
||||
if (format().samples() != 0) {
|
||||
QRect rect(QPoint(0, 0), size());
|
||||
if (extraFuncs->hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets)) {
|
||||
QOpenGLFramebufferObject temp(d->colorAttachments[colorAttachmentIndex].size, QOpenGLFramebufferObjectFormat());
|
||||
blitFramebuffer(&temp, rect, const_cast<QOpenGLFramebufferObject *>(this), rect,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST,
|
||||
colorAttachmentIndex, 0);
|
||||
image = temp.toImage(flipped);
|
||||
} else {
|
||||
QOpenGLFramebufferObject temp(size(), QOpenGLFramebufferObjectFormat());
|
||||
blitFramebuffer(&temp, rect, const_cast<QOpenGLFramebufferObject *>(this), rect);
|
||||
image = temp.toImage(flipped);
|
||||
}
|
||||
} else {
|
||||
if (extraFuncs->hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets)) {
|
||||
extraFuncs->glReadBuffer(GL_COLOR_ATTACHMENT0 + colorAttachmentIndex);
|
||||
image = qt_gl_read_framebuffer(d->colorAttachments[colorAttachmentIndex].size,
|
||||
d->colorAttachments[colorAttachmentIndex].internalFormat,
|
||||
true, flipped);
|
||||
extraFuncs->glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
} else {
|
||||
image = qt_gl_read_framebuffer(d->colorAttachments[0].size,
|
||||
d->colorAttachments[0].internalFormat,
|
||||
true, flipped);
|
||||
}
|
||||
}
|
||||
|
||||
if (prevFbo != d->fbo())
|
||||
ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, prevFbo);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1366,7 +1540,7 @@ void QOpenGLFramebufferObject::setAttachment(QOpenGLFramebufferObject::Attachmen
|
||||
#endif
|
||||
d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo());
|
||||
QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true;
|
||||
d->initAttachments(current, attachment);
|
||||
d->initDepthStencilAttachments(current, attachment);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1428,6 +1602,18 @@ void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target,
|
||||
buffers, filter);
|
||||
}
|
||||
|
||||
/*! \overload
|
||||
*
|
||||
Convenience overload to blit between two framebuffer objects.
|
||||
*/
|
||||
void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
|
||||
QOpenGLFramebufferObject *source, const QRect &sourceRect,
|
||||
GLbitfield buffers,
|
||||
GLenum filter)
|
||||
{
|
||||
blitFramebuffer(target, targetRect, source, sourceRect, buffers, filter, 0, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Blits from the \a sourceRect rectangle in the \a source framebuffer
|
||||
object to the \a targetRect rectangle in the \a target framebuffer object.
|
||||
@ -1456,12 +1642,18 @@ void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target,
|
||||
|
||||
\note The scissor test will restrict the blit area if enabled.
|
||||
|
||||
When multiple render targets are in use, \a readColorAttachmentIndex and \a
|
||||
drawColorAttachmentIndex specify the index of the color attachments in the
|
||||
source and destination framebuffers.
|
||||
|
||||
\sa hasOpenGLFramebufferBlit()
|
||||
*/
|
||||
void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
|
||||
QOpenGLFramebufferObject *source, const QRect &sourceRect,
|
||||
GLbitfield buffers,
|
||||
GLenum filter)
|
||||
QOpenGLFramebufferObject *source, const QRect &sourceRect,
|
||||
GLbitfield buffers,
|
||||
GLenum filter,
|
||||
int readColorAttachmentIndex,
|
||||
int drawColorAttachmentIndex)
|
||||
{
|
||||
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
||||
if (!ctx)
|
||||
@ -1489,10 +1681,21 @@ void QOpenGLFramebufferObject::blitFramebuffer(QOpenGLFramebufferObject *target,
|
||||
extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, source ? source->handle() : defaultFboId);
|
||||
extensions.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target ? target->handle() : defaultFboId);
|
||||
|
||||
if (extensions.hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets)) {
|
||||
extensions.glReadBuffer(GL_COLOR_ATTACHMENT0 + readColorAttachmentIndex);
|
||||
if (target) {
|
||||
GLenum drawBuf = GL_COLOR_ATTACHMENT0 + drawColorAttachmentIndex;
|
||||
extensions.glDrawBuffers(1, &drawBuf);
|
||||
}
|
||||
}
|
||||
|
||||
extensions.glBlitFramebuffer(sx0, sy0, sx1, sy1,
|
||||
tx0, ty0, tx1, ty1,
|
||||
buffers, filter);
|
||||
|
||||
if (extensions.hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets))
|
||||
extensions.glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
|
||||
ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, prevFbo); // sets both READ and DRAW
|
||||
}
|
||||
|
||||
|
@ -63,15 +63,18 @@ public:
|
||||
QOpenGLFramebufferObject(int width, int height, GLenum target = GL_TEXTURE_2D);
|
||||
|
||||
QOpenGLFramebufferObject(const QSize &size, Attachment attachment,
|
||||
GLenum target = GL_TEXTURE_2D, GLenum internal_format = 0);
|
||||
GLenum target = GL_TEXTURE_2D, GLenum internalFormat = 0);
|
||||
QOpenGLFramebufferObject(int width, int height, Attachment attachment,
|
||||
GLenum target = GL_TEXTURE_2D, GLenum internal_format = 0);
|
||||
GLenum target = GL_TEXTURE_2D, GLenum internalFormat = 0);
|
||||
|
||||
QOpenGLFramebufferObject(const QSize &size, const QOpenGLFramebufferObjectFormat &format);
|
||||
QOpenGLFramebufferObject(int width, int height, const QOpenGLFramebufferObjectFormat &format);
|
||||
|
||||
virtual ~QOpenGLFramebufferObject();
|
||||
|
||||
void addColorAttachment(const QSize &size, GLenum internalFormat = 0);
|
||||
void addColorAttachment(int width, int height, GLenum internalFormat = 0);
|
||||
|
||||
QOpenGLFramebufferObjectFormat format() const;
|
||||
|
||||
bool isValid() const;
|
||||
@ -83,12 +86,19 @@ public:
|
||||
int height() const { return size().height(); }
|
||||
|
||||
GLuint texture() const;
|
||||
QVector<GLuint> textures() const;
|
||||
|
||||
GLuint takeTexture();
|
||||
GLuint takeTexture(int colorAttachmentIndex);
|
||||
|
||||
QSize size() const;
|
||||
QVector<QSize> sizes() const;
|
||||
|
||||
QImage toImage() const;
|
||||
QImage toImage(bool flipped) const;
|
||||
Attachment attachment() const;
|
||||
QImage toImage(bool flipped, int colorAttachmentIndex) const;
|
||||
|
||||
Attachment attachment() const;
|
||||
void setAttachment(Attachment attachment);
|
||||
|
||||
GLuint handle() const;
|
||||
@ -98,6 +108,12 @@ public:
|
||||
static bool hasOpenGLFramebufferObjects();
|
||||
|
||||
static bool hasOpenGLFramebufferBlit();
|
||||
static void blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
|
||||
QOpenGLFramebufferObject *source, const QRect &sourceRect,
|
||||
GLbitfield buffers,
|
||||
GLenum filter,
|
||||
int readColorAttachmentIndex,
|
||||
int drawColorAttachmentIndex);
|
||||
static void blitFramebuffer(QOpenGLFramebufferObject *target, const QRect &targetRect,
|
||||
QOpenGLFramebufferObject *source, const QRect &sourceRect,
|
||||
GLbitfield buffers = GL_COLOR_BUFFER_BIT,
|
||||
|
@ -102,32 +102,41 @@ public:
|
||||
class QOpenGLFramebufferObjectPrivate
|
||||
{
|
||||
public:
|
||||
QOpenGLFramebufferObjectPrivate() : fbo_guard(0), texture_guard(0), depth_buffer_guard(0)
|
||||
, stencil_buffer_guard(0), color_buffer_guard(0)
|
||||
QOpenGLFramebufferObjectPrivate() : fbo_guard(0), depth_buffer_guard(0)
|
||||
, stencil_buffer_guard(0)
|
||||
, valid(false) {}
|
||||
~QOpenGLFramebufferObjectPrivate() {}
|
||||
|
||||
void init(QOpenGLFramebufferObject *q, const QSize& sz,
|
||||
void init(QOpenGLFramebufferObject *q, const QSize &size,
|
||||
QOpenGLFramebufferObject::Attachment attachment,
|
||||
GLenum internal_format, GLenum texture_target,
|
||||
GLenum texture_target, GLenum internal_format,
|
||||
GLint samples = 0, bool mipmap = false);
|
||||
void initTexture(GLenum target, GLenum internal_format, const QSize &size, bool mipmap);
|
||||
void initAttachments(QOpenGLContext *ctx, QOpenGLFramebufferObject::Attachment attachment);
|
||||
void initTexture(int idx);
|
||||
void initColorBuffer(int idx, GLint *samples);
|
||||
void initDepthStencilAttachments(QOpenGLContext *ctx, QOpenGLFramebufferObject::Attachment attachment);
|
||||
|
||||
bool checkFramebufferStatus(QOpenGLContext *ctx) const;
|
||||
QOpenGLSharedResourceGuard *fbo_guard;
|
||||
QOpenGLSharedResourceGuard *texture_guard;
|
||||
QOpenGLSharedResourceGuard *depth_buffer_guard;
|
||||
QOpenGLSharedResourceGuard *stencil_buffer_guard;
|
||||
QOpenGLSharedResourceGuard *color_buffer_guard;
|
||||
GLenum target;
|
||||
QSize size;
|
||||
QSize dsSize;
|
||||
QOpenGLFramebufferObjectFormat format;
|
||||
int requestedSamples;
|
||||
uint valid : 1;
|
||||
QOpenGLFramebufferObject::Attachment fbo_attachment;
|
||||
QOpenGLExtensions funcs;
|
||||
|
||||
struct ColorAttachment {
|
||||
ColorAttachment() : internalFormat(0), guard(0) { }
|
||||
ColorAttachment(const QSize &size, GLenum internalFormat)
|
||||
: size(size), internalFormat(internalFormat), guard(0) { }
|
||||
QSize size;
|
||||
GLenum internalFormat;
|
||||
QOpenGLSharedResourceGuard *guard;
|
||||
};
|
||||
QVector<ColorAttachment> colorAttachments;
|
||||
|
||||
inline GLuint fbo() const { return fbo_guard ? fbo_guard->id() : 0; }
|
||||
};
|
||||
|
||||
|
@ -184,6 +184,7 @@ Q_LOGGING_CATEGORY(lcGLES3, "qt.opengl.es3")
|
||||
\value NPOTTextureRepeat Non power of two textures can use GL_REPEAT as wrap parameter.
|
||||
\value FixedFunctionPipeline The fixed function pipeline is available.
|
||||
\value TextureRGFormats The GL_RED and GL_RG texture formats are available.
|
||||
\value MultipleRenderTargets Multiple color attachments to framebuffer objects are available.
|
||||
*/
|
||||
|
||||
// Hidden private fields for additional extension data.
|
||||
@ -275,7 +276,7 @@ static int qt_gl_resolve_features()
|
||||
{
|
||||
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
||||
if (ctx->isOpenGLES()) {
|
||||
// OpenGL ES 2
|
||||
// OpenGL ES
|
||||
int features = QOpenGLFunctions::Multitexture |
|
||||
QOpenGLFunctions::Shaders |
|
||||
QOpenGLFunctions::Buffers |
|
||||
@ -300,6 +301,8 @@ static int qt_gl_resolve_features()
|
||||
if (!(renderer && strstr(renderer, "Mesa")))
|
||||
features |= QOpenGLFunctions::TextureRGFormats;
|
||||
}
|
||||
if (ctx->format().majorVersion() >= 3)
|
||||
features |= QOpenGLFunctions::MultipleRenderTargets;
|
||||
return features;
|
||||
} else {
|
||||
// OpenGL
|
||||
@ -308,10 +311,9 @@ static int qt_gl_resolve_features()
|
||||
QOpenGLExtensionMatcher extensions;
|
||||
|
||||
if (format.majorVersion() >= 3)
|
||||
features |= QOpenGLFunctions::Framebuffers;
|
||||
else if (extensions.match("GL_EXT_framebuffer_object") ||
|
||||
extensions.match("GL_ARB_framebuffer_object"))
|
||||
features |= QOpenGLFunctions::Framebuffers;
|
||||
features |= QOpenGLFunctions::Framebuffers | QOpenGLFunctions::MultipleRenderTargets;
|
||||
else if (extensions.match("GL_EXT_framebuffer_object") || extensions.match("GL_ARB_framebuffer_object"))
|
||||
features |= QOpenGLFunctions::Framebuffers | QOpenGLFunctions::MultipleRenderTargets;
|
||||
|
||||
if (format.majorVersion() >= 2) {
|
||||
features |= QOpenGLFunctions::BlendColor |
|
||||
|
@ -241,7 +241,8 @@ public:
|
||||
NPOTTextures = 0x1000,
|
||||
NPOTTextureRepeat = 0x2000,
|
||||
FixedFunctionPipeline = 0x4000,
|
||||
TextureRGFormats = 0x8000
|
||||
TextureRGFormats = 0x8000,
|
||||
MultipleRenderTargets = 0x10000
|
||||
};
|
||||
Q_DECLARE_FLAGS(OpenGLFeatures, OpenGLFeature)
|
||||
|
||||
|
@ -86,6 +86,8 @@ private slots:
|
||||
void fboRenderingRGB30_data();
|
||||
void fboRenderingRGB30();
|
||||
void fboHandleNulledAfterContextDestroyed();
|
||||
void fboMRT();
|
||||
void fboMRT_differentFormats();
|
||||
void openGLPaintDevice_data();
|
||||
void openGLPaintDevice();
|
||||
void aboutToBeDestroyed();
|
||||
@ -594,6 +596,14 @@ void tst_QOpenGL::fboRenderingRGB30_data()
|
||||
common_data();
|
||||
}
|
||||
|
||||
#ifndef GL_RGB5_A1
|
||||
#define GL_RGB5_A1 0x8057
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGBA8
|
||||
#define GL_RGBA8 0x8058
|
||||
#endif
|
||||
|
||||
#ifndef GL_RGB10_A2
|
||||
#define GL_RGB10_A2 0x8059
|
||||
#endif
|
||||
@ -606,6 +616,24 @@ void tst_QOpenGL::fboRenderingRGB30_data()
|
||||
#define GL_FULL_SUPPORT 0x82B7
|
||||
#endif
|
||||
|
||||
static bool hasRGB10A2(QOpenGLContext *ctx)
|
||||
{
|
||||
if (ctx->format().majorVersion() < 3)
|
||||
return false;
|
||||
#ifndef QT_OPENGL_ES_2
|
||||
if (!ctx->isOpenGLES() && ctx->format().majorVersion() >= 4) {
|
||||
GLint value = -1;
|
||||
QOpenGLFunctions_4_2_Core* vFuncs = ctx->versionFunctions<QOpenGLFunctions_4_2_Core>();
|
||||
if (vFuncs && vFuncs->initializeOpenGLFunctions()) {
|
||||
vFuncs->glGetInternalformativ(GL_TEXTURE_2D, GL_RGB10_A2, GL_FRAMEBUFFER_RENDERABLE, 1, &value);
|
||||
if (value != GL_FULL_SUPPORT)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void tst_QOpenGL::fboRenderingRGB30()
|
||||
{
|
||||
#if defined(Q_OS_LINUX) && defined(Q_CC_GNU) && !defined(__x86_64__)
|
||||
@ -623,24 +651,9 @@ void tst_QOpenGL::fboRenderingRGB30()
|
||||
if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects())
|
||||
QSKIP("QOpenGLFramebufferObject not supported on this platform");
|
||||
|
||||
if (ctx.format().majorVersion() < 3)
|
||||
if (!hasRGB10A2(&ctx))
|
||||
QSKIP("An internal RGB30_A2 format is not guaranteed on this platform");
|
||||
|
||||
#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_2)
|
||||
// NVidia currently only supports RGB30 and RGB30_A2 in their Quadro drivers,
|
||||
// but they do provide an extension for querying the support. We use the query
|
||||
// in case they implement the required formats later.
|
||||
if (!ctx.isOpenGLES() && ctx.format().majorVersion() >= 4) {
|
||||
GLint value = -1;
|
||||
QOpenGLFunctions_4_2_Core* vFuncs = ctx.versionFunctions<QOpenGLFunctions_4_2_Core>();
|
||||
if (vFuncs && vFuncs->initializeOpenGLFunctions()) {
|
||||
vFuncs->glGetInternalformativ(GL_TEXTURE_2D, GL_RGB10_A2, GL_FRAMEBUFFER_RENDERABLE, 1, &value);
|
||||
if (value != GL_FULL_SUPPORT)
|
||||
QSKIP("The required RGB30_A2 format is not supported by this driver");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// No multisample with combined depth/stencil attachment:
|
||||
QOpenGLFramebufferObjectFormat fboFormat;
|
||||
fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
|
||||
@ -720,6 +733,150 @@ void tst_QOpenGL::fboHandleNulledAfterContextDestroyed()
|
||||
QCOMPARE(fbo->handle(), 0U);
|
||||
}
|
||||
|
||||
void tst_QOpenGL::fboMRT()
|
||||
{
|
||||
QWindow window;
|
||||
window.setSurfaceType(QWindow::OpenGLSurface);
|
||||
window.setGeometry(0, 0, 10, 10);
|
||||
window.create();
|
||||
|
||||
QOpenGLContext ctx;
|
||||
QVERIFY(ctx.create());
|
||||
ctx.makeCurrent(&window);
|
||||
|
||||
if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects())
|
||||
QSKIP("QOpenGLFramebufferObject not supported on this platform");
|
||||
|
||||
if (!ctx.functions()->hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets))
|
||||
QSKIP("Multiple render targets not supported on this platform");
|
||||
|
||||
QOpenGLExtraFunctions *ef = ctx.extraFunctions();
|
||||
|
||||
{
|
||||
// 3 color attachments, different sizes, same internal format, no depth/stencil.
|
||||
QVector<QSize> sizes;
|
||||
sizes << QSize(128, 128) << QSize(192, 128) << QSize(432, 123);
|
||||
QOpenGLFramebufferObject fbo(sizes[0]);
|
||||
fbo.addColorAttachment(sizes[1]);
|
||||
fbo.addColorAttachment(sizes[2]);
|
||||
QVERIFY(fbo.bind());
|
||||
QCOMPARE(fbo.attachment(), QOpenGLFramebufferObject::NoAttachment);
|
||||
QCOMPARE(sizes, fbo.sizes());
|
||||
QCOMPARE(sizes[0], fbo.size());
|
||||
// Clear the three buffers to red, green and blue.
|
||||
GLenum drawBuf = GL_COLOR_ATTACHMENT0;
|
||||
ef->glDrawBuffers(1, &drawBuf);
|
||||
ef->glClearColor(1, 0, 0, 1);
|
||||
ef->glClear(GL_COLOR_BUFFER_BIT);
|
||||
drawBuf = GL_COLOR_ATTACHMENT0 + 1;
|
||||
ef->glDrawBuffers(1, &drawBuf);
|
||||
ef->glClearColor(0, 1, 0, 1);
|
||||
ef->glClear(GL_COLOR_BUFFER_BIT);
|
||||
drawBuf = GL_COLOR_ATTACHMENT0 + 2;
|
||||
ef->glDrawBuffers(1, &drawBuf);
|
||||
ef->glClearColor(0, 0, 1, 1);
|
||||
ef->glClear(GL_COLOR_BUFFER_BIT);
|
||||
// Verify, keeping in mind that only a 128x123 area is touched in the buffers.
|
||||
// Some drivers do not get this right, unfortunately, so do not rely on it.
|
||||
const char *vendor = (const char *) ef->glGetString(GL_VENDOR);
|
||||
bool hasCorrectMRT = false;
|
||||
if (vendor && strstr(vendor, "NVIDIA")) // maybe others too
|
||||
hasCorrectMRT = true;
|
||||
QImage img = fbo.toImage(false, 0);
|
||||
QCOMPARE(img.size(), sizes[0]);
|
||||
QCOMPARE(img.pixel(0, 0), qRgb(255, 0, 0));
|
||||
if (hasCorrectMRT)
|
||||
QCOMPARE(img.pixel(127, 122), qRgb(255, 0, 0));
|
||||
img = fbo.toImage(false, 1);
|
||||
QCOMPARE(img.size(), sizes[1]);
|
||||
QCOMPARE(img.pixel(0, 0), qRgb(0, 255, 0));
|
||||
if (hasCorrectMRT)
|
||||
QCOMPARE(img.pixel(127, 122), qRgb(0, 255, 0));
|
||||
img = fbo.toImage(false, 2);
|
||||
QCOMPARE(img.size(), sizes[2]);
|
||||
QCOMPARE(img.pixel(0, 0), qRgb(0, 0, 255));
|
||||
if (hasCorrectMRT)
|
||||
QCOMPARE(img.pixel(127, 122), qRgb(0, 0, 255));
|
||||
fbo.release();
|
||||
}
|
||||
|
||||
{
|
||||
// 2 color attachments, same size, same internal format, depth/stencil.
|
||||
QVector<QSize> sizes;
|
||||
sizes.fill(QSize(128, 128), 2);
|
||||
QOpenGLFramebufferObject fbo(sizes[0], QOpenGLFramebufferObject::CombinedDepthStencil);
|
||||
fbo.addColorAttachment(sizes[1]);
|
||||
QVERIFY(fbo.bind());
|
||||
QCOMPARE(fbo.attachment(), QOpenGLFramebufferObject::CombinedDepthStencil);
|
||||
QCOMPARE(sizes, fbo.sizes());
|
||||
QCOMPARE(sizes[0], fbo.size());
|
||||
ef->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
ef->glFinish();
|
||||
fbo.release();
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QOpenGL::fboMRT_differentFormats()
|
||||
{
|
||||
QWindow window;
|
||||
window.setSurfaceType(QWindow::OpenGLSurface);
|
||||
window.setGeometry(0, 0, 10, 10);
|
||||
window.create();
|
||||
|
||||
QOpenGLContext ctx;
|
||||
QVERIFY(ctx.create());
|
||||
ctx.makeCurrent(&window);
|
||||
|
||||
QOpenGLFunctions *f = ctx.functions();
|
||||
const char * vendor = (const char *) f->glGetString(GL_VENDOR);
|
||||
if (vendor && strstr(vendor, "VMware, Inc."))
|
||||
QSKIP("The tested formats may not be supported on this platform");
|
||||
|
||||
if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects())
|
||||
QSKIP("QOpenGLFramebufferObject not supported on this platform");
|
||||
|
||||
if (!f->hasOpenGLFeature(QOpenGLFunctions::MultipleRenderTargets))
|
||||
QSKIP("Multiple render targets not supported on this platform");
|
||||
|
||||
if (!hasRGB10A2(&ctx))
|
||||
QSKIP("RGB10_A2 not supported on this platform");
|
||||
|
||||
// 3 color attachments, same size, different internal format, depth/stencil.
|
||||
QVector<QSize> sizes;
|
||||
sizes.fill(QSize(128, 128), 3);
|
||||
QOpenGLFramebufferObjectFormat format;
|
||||
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
|
||||
QVector<GLenum> internalFormats;
|
||||
internalFormats << GL_RGBA8 << GL_RGB10_A2 << GL_RGB5_A1;
|
||||
format.setInternalTextureFormat(internalFormats[0]);
|
||||
QOpenGLFramebufferObject fbo(sizes[0], format);
|
||||
fbo.addColorAttachment(sizes[1], internalFormats[1]);
|
||||
fbo.addColorAttachment(sizes[2], internalFormats[2]);
|
||||
|
||||
QVERIFY(fbo.bind());
|
||||
QCOMPARE(fbo.attachment(), QOpenGLFramebufferObject::CombinedDepthStencil);
|
||||
QCOMPARE(sizes, fbo.sizes());
|
||||
QCOMPARE(sizes[0], fbo.size());
|
||||
|
||||
QOpenGLExtraFunctions *ef = ctx.extraFunctions();
|
||||
QVERIFY(ef->glGetError() == 0);
|
||||
ef->glClearColor(1, 0, 0, 1);
|
||||
GLenum drawBuf[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT0 + 1, GL_COLOR_ATTACHMENT0 + 2 };
|
||||
ef->glDrawBuffers(3, drawBuf);
|
||||
ef->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
QVERIFY(ef->glGetError() == 0);
|
||||
|
||||
QImage img = fbo.toImage(true, 0);
|
||||
QCOMPARE(img.size(), sizes[0]);
|
||||
QCOMPARE(img.pixel(0, 0), qRgb(255, 0, 0));
|
||||
img = fbo.toImage(true, 1);
|
||||
QCOMPARE(img.size(), sizes[1]);
|
||||
QCOMPARE(img.format(), QImage::Format_A2BGR30_Premultiplied);
|
||||
QCOMPARE(img.pixel(0, 0), qRgb(255, 0, 0));
|
||||
|
||||
fbo.release();
|
||||
}
|
||||
|
||||
void tst_QOpenGL::imageFormatPainting()
|
||||
{
|
||||
QScopedPointer<QSurface> surface(createSurface(QSurface::Window));
|
||||
|
Loading…
x
Reference in New Issue
Block a user