rhi: gl: Add a feature flag for reading back non-zero mip levels

The joys of "level - Specifies the mipmap level of the texture
image to be attached, which must be 0." for glFramebufferTexture2D
in OpenGL ES 2.0.

Change-Id: Iaf19502f48d7ba73b26abb72535bfa6696a1e182
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
Laszlo Agocs 2019-10-05 16:05:06 +02:00
parent 32924110ce
commit 9baf69c765
8 changed files with 53 additions and 16 deletions

View File

@ -572,7 +572,13 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
supported for QRhiBuffer instances with a usage different than
UniformBuffer. While this is supported in the majority of cases, it will be
unsupported with OpenGL ES older than 3.0.
*/
\value ReadBackNonBaseMipLevel Indicates that specifying a mip level other
than 0 is supported when reading back texture contents. When not supported,
specifying a non-zero level in QRhiReadbackDescription leads to returning
an all-zero image. In practice this feature will be unsupported with OpenGL
ES 2.0, while it will likely be supported everywhere else.
*/
/*!
\enum QRhi::BeginFrameFlag

View File

@ -1436,7 +1436,8 @@ public:
BaseVertex,
BaseInstance,
TriangleFanTopology,
ReadBackNonUniformBuffer
ReadBackNonUniformBuffer,
ReadBackNonBaseMipLevel
};
enum BeginFrameFlag {

View File

@ -473,6 +473,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return false;
case QRhi::ReadBackNonUniformBuffer:
return true;
case QRhi::ReadBackNonBaseMipLevel:
return true;
default:
Q_UNREACHABLE();
return false;

View File

@ -500,6 +500,11 @@ bool QRhiGles2::create(QRhi::Flags flags)
// extension(s) (which is not in ES 3.0...messy)
caps.properMapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBufferRange);
if (caps.gles)
caps.nonBaseLevelFramebufferTexture = caps.ctxMajor >= 3; // ES 3.0
else
caps.nonBaseLevelFramebufferTexture = true;
if (!caps.gles) {
f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
f->glEnable(GL_POINT_SPRITE);
@ -744,6 +749,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::ReadBackNonUniformBuffer:
return !caps.gles || caps.properMapBuffer;
case QRhi::ReadBackNonBaseMipLevel:
return caps.nonBaseLevelFramebufferTexture;
default:
Q_UNREACHABLE();
return false;
@ -2156,23 +2163,31 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
QRhiReadbackResult *result = cmd.args.readPixels.result;
GLuint tex = cmd.args.readPixels.texture;
GLuint fbo = 0;
int mipLevel = 0;
if (tex) {
result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h);
result->format = cmd.args.readPixels.format;
f->glGenFramebuffers(1, &fbo);
f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, cmd.args.readPixels.level);
mipLevel = cmd.args.readPixels.level;
if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
f->glGenFramebuffers(1, &fbo);
f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, mipLevel);
}
} else {
result->pixelSize = currentSwapChain->pixelSize;
result->format = QRhiTexture::RGBA8;
// readPixels handles multisample resolving implicitly
}
result->data.resize(result->pixelSize.width() * result->pixelSize.height() * 4);
// With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it.
f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(),
GL_RGBA, GL_UNSIGNED_BYTE,
result->data.data());
if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
// With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it.
f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(),
GL_RGBA, GL_UNSIGNED_BYTE,
result->data.data());
} else {
result->data.fill('\0');
}
if (fbo) {
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
f->glDeleteFramebuffers(1, &fbo);

View File

@ -753,7 +753,8 @@ public:
baseVertex(false),
compute(false),
textureCompareMode(false),
properMapBuffer(false)
properMapBuffer(false),
nonBaseLevelFramebufferTexture(false)
{ }
int ctxMajor;
int ctxMinor;
@ -785,6 +786,7 @@ public:
uint compute : 1;
uint textureCompareMode : 1;
uint properMapBuffer : 1;
uint nonBaseLevelFramebufferTexture : 1;
} caps;
QGles2SwapChain *currentSwapChain = nullptr;
QVector<GLint> supportedCompressedFormats;

View File

@ -559,6 +559,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return false;
case QRhi::ReadBackNonUniformBuffer:
return true;
case QRhi::ReadBackNonBaseMipLevel:
return true;
default:
Q_UNREACHABLE();
return false;

View File

@ -3823,6 +3823,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::ReadBackNonUniformBuffer:
return true;
case QRhi::ReadBackNonBaseMipLevel:
return true;
default:
Q_UNREACHABLE();
return false;

View File

@ -277,7 +277,8 @@ void tst_QRhi::create()
QRhi::BaseVertex,
QRhi::BaseInstance,
QRhi::TriangleFanTopology,
QRhi::ReadBackNonUniformBuffer
QRhi::ReadBackNonUniformBuffer,
QRhi::ReadBackNonBaseMipLevel
};
for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->isFeatureSupported(features[i]);
@ -965,10 +966,16 @@ void tst_QRhi::resourceUpdateBatchRGBATextureMip()
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
readResult.pixelSize.width(), readResult.pixelSize.height(),
red.format());
// Compare to a scaled version; we can do this safely only because we
// only have plain red pixels in the source image.
QImage expectedImage = red.scaled(expectedSize);
QImage expectedImage;
if (level == 0 || rhi->isFeatureSupported(QRhi::ReadBackNonBaseMipLevel)) {
// Compare to a scaled version; we can do this safely only because we
// only have plain red pixels in the source image.
expectedImage = red.scaled(expectedSize);
} else {
qDebug("Expecting all-zero image for level %d because reading back a level other than 0 is not supported", level);
expectedImage = QImage(readResult.pixelSize, red.format());
expectedImage.fill(0);
}
QVERIFY(imageRGBAEquals(expectedImage, wrapperImage));
}
}