rhi: Enable reading back slices of 3D textures

Change-Id: I0c687677b7e86b7284130c775718b29aca2cca40
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2021-05-28 19:23:49 +02:00
parent 4652da536a
commit 64089b900f
8 changed files with 118 additions and 32 deletions

View File

@ -2426,8 +2426,7 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src)
\value ThreeDimensional The texture is a 3D texture. Such textures should
be created with the QRhi::newTexture() overload taking a depth in addition
to width and height. A 3D texture can have mipmaps but cannot be
multisample. Reading back the contents of a 3D texture is not currently
supported. When rendering into a 3D texture, the layer specified in the
multisample. When rendering into a 3D texture, the layer specified in the
render target's color attachment refers to a slice in range [0..depth-1].
The underlying graphics API may not support 3D textures at run time.
Support is indicated by the QRhi::ThreeDimensionalTextures feature.
@ -5322,8 +5321,6 @@ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, co
\note Multisample textures cannot be read back.
\note 3D textures cannot be read back.
\note The readback returns raw byte data, in order to allow the applications
to interpret it in any way they see fit. Be aware of the blending settings
of rendering code: if the blending is set up to rely on premultiplied alpha,
@ -5342,6 +5339,10 @@ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, co
N is the \l{QRhi::resourceLimit()}{resource limit value} returned for
QRhi::MaxAsyncReadbackFrames.
A single readback operation copies one mip level of one layer (cubemap face
or 3D slice) at a time. The level and layer are specified by the respective
fields in \a rb.
\sa readBackBuffer(), QRhi::resourceLimit()
*/
void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)

View File

@ -1564,23 +1564,19 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
UINT subres = 0;
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.rb.texture());
QD3D11SwapChain *swapChainD = nullptr;
bool is3D = false;
if (texD) {
if (texD->sampleDesc.Count > 1) {
qWarning("Multisample texture cannot be read back");
continue;
}
// No support for reading back 3D, not because it is not
// possible technically, but we need to draw the line somewhere.
if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional)) {
qWarning("3D texture cannot be read back");
continue;
}
src = texD->textureResource();
dxgiFormat = texD->dxgiFormat;
pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
format = texD->m_format;
subres = D3D11CalcSubresource(UINT(u.rb.level()), UINT(u.rb.layer()), texD->mipLevelCount);
is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
subres = D3D11CalcSubresource(UINT(u.rb.level()), UINT(is3D ? 0 : u.rb.layer()), texD->mipLevelCount);
} else {
Q_ASSERT(contextState.currentSwapChain);
swapChainD = QRHI_RES(QD3D11SwapChain, contextState.currentSwapChain);
@ -1635,7 +1631,18 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
cmd.args.copySubRes.dstZ = 0;
cmd.args.copySubRes.src = src;
cmd.args.copySubRes.srcSubRes = subres;
cmd.args.copySubRes.hasSrcBox = false;
if (is3D) {
D3D11_BOX srcBox;
memset(&srcBox, 0, sizeof(srcBox));
srcBox.front = UINT(u.rb.layer());
srcBox.right = desc.Width; // exclusive
srcBox.bottom = desc.Height;
srcBox.back = srcBox.front + 1;
cmd.args.copySubRes.hasSrcBox = true;
cmd.args.copySubRes.srcBox = srcBox;
} else {
cmd.args.copySubRes.hasSrcBox = false;
}
readback.stagingTex = stagingTex;
readback.byteSize = byteSize;

View File

@ -2045,14 +2045,20 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
if (texD)
trackedImageBarrier(cbD, texD, QGles2Texture::AccessRead);
cmd.args.readPixels.texture = texD ? texD->texture : 0;
cmd.args.readPixels.slice3D = -1;
if (texD) {
const QSize readImageSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
cmd.args.readPixels.w = readImageSize.width();
cmd.args.readPixels.h = readImageSize.height();
cmd.args.readPixels.format = texD->m_format;
const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap)
? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
cmd.args.readPixels.readTarget = faceTargetBase + uint(u.rb.layer());
if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional)) {
cmd.args.readPixels.readTarget = texD->target;
cmd.args.readPixels.slice3D = u.rb.layer();
} else {
const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap)
? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
cmd.args.readPixels.readTarget = faceTargetBase + uint(u.rb.layer());
}
cmd.args.readPixels.level = u.rb.level();
}
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
@ -2802,8 +2808,13 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
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);
if (cmd.args.readPixels.slice3D >= 0) {
f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
tex, mipLevel, cmd.args.readPixels.slice3D);
} else {
f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
cmd.args.readPixels.readTarget, tex, mipLevel);
}
}
} else {
result->pixelSize = currentSwapChain->pixelSize;

View File

@ -467,6 +467,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
QRhiTexture::Format format;
GLenum readTarget;
int level;
int slice3D;
} readPixels;
struct {
GLenum target;

View File

@ -1854,15 +1854,13 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
QMetalSwapChain *swapChainD = nullptr;
id<MTLTexture> src;
QSize srcSize;
bool is3D = false;
if (texD) {
if (texD->samples > 1) {
qWarning("Multisample texture cannot be read back");
continue;
}
if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional)) {
qWarning("3D texture readback is not implemented");
continue;
}
is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
readback.format = texD->m_format;
src = texD->d->tex;
@ -1890,9 +1888,9 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
ensureBlit();
[blitEnc copyFromTexture: src
sourceSlice: NSUInteger(u.rb.layer())
sourceSlice: NSUInteger(is3D ? 0 : u.rb.layer())
sourceLevel: NSUInteger(u.rb.level())
sourceOrigin: MTLOriginMake(0, 0, 0)
sourceOrigin: MTLOriginMake(0, 0, is3D ? u.rb.layer() : 0)
sourceSize: MTLSizeMake(NSUInteger(srcSize.width()), NSUInteger(srcSize.height()), 1)
toBuffer: readback.buf
destinationOffset: 0

View File

@ -3347,15 +3347,13 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture());
QVkSwapChain *swapChainD = nullptr;
bool is3D = false;
if (texD) {
if (texD->samples > VK_SAMPLE_COUNT_1_BIT) {
qWarning("Multisample texture cannot be read back");
continue;
}
if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional)) {
qWarning("3D texture cannot be read back");
continue;
}
is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
readback.format = texD->m_format;
texD->lastActiveFrameSlot = currentFrameSlot;
@ -3405,8 +3403,10 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
copyDesc.bufferOffset = 0;
copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level());
copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.rb.layer());
copyDesc.imageSubresource.baseArrayLayer = is3D ? 0 : uint32_t(u.rb.layer());
copyDesc.imageSubresource.layerCount = 1;
if (is3D)
copyDesc.imageOffset.z = u.rb.layer();
copyDesc.imageExtent.width = uint32_t(readback.pixelSize.width());
copyDesc.imageExtent.height = uint32_t(readback.pixelSize.height());
copyDesc.imageExtent.depth = 1;

View File

@ -4,3 +4,6 @@ android
# QTBUG-92211
[renderPassDescriptorCompatibility]
android
# Skip 3D textures with Android emulator, the sw-based GL there is no good
[threeDimTexture]
android

View File

@ -910,10 +910,8 @@ void tst_QRhi::resourceUpdateBatchBuffer()
}
}
inline bool imageRGBAEquals(const QImage &a, const QImage &b)
inline bool imageRGBAEquals(const QImage &a, const QImage &b, int maxFuzz = 1)
{
const int maxFuzz = 1;
if (a.size() != b.size())
return false;
@ -3882,6 +3880,33 @@ void tst_QRhi::threeDimTexture()
batch->generateMips(texture.data());
QVERIFY(submitResourceUpdates(rhi.data(), batch));
// read back slice 63 of level 1 (256x128, almost red)
batch = rhi->nextResourceUpdateBatch();
QRhiReadbackResult readResult;
QImage result;
readResult.completed = [&readResult, &result] {
result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
readResult.pixelSize.width(), readResult.pixelSize.height(),
QImage::Format_RGBA8888);
};
QRhiReadbackDescription readbackDescription(texture.data());
readbackDescription.setLevel(1);
readbackDescription.setLayer(63);
batch->readBackTexture(readbackDescription, &readResult);
QVERIFY(submitResourceUpdates(rhi.data(), batch));
QVERIFY(!result.isNull());
QImage referenceImage(WIDTH / 2, HEIGHT / 2, result.format());
referenceImage.fill(QColor::fromRgb(253, 0, 0));
// Now restrict the test a bit. The Null QRhi backend has broken support for
// mipmap generation of 3D textures (it ignores the depth, effectively behaving as
// if the 3D texture was a 2D array which is incorrect wrt mipmapping)
// Some software-based OpenGL implementations, such as Mesa llvmpipe builds that are
// used both in Qt CI and are shipped with the official Qt binaries also seem to have
// problems with this.
if (impl != QRhi::Null && impl != QRhi::OpenGLES2)
QVERIFY(imageRGBAEquals(result, referenceImage, 2));
}
// render target (one slice)
@ -3889,7 +3914,7 @@ void tst_QRhi::threeDimTexture()
{
const int SLICE = 23;
QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, WIDTH, HEIGHT, DEPTH,
1, QRhiTexture::RenderTarget));
1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
QVERIFY(texture->create());
QRhiColorAttachment att(texture.data());
@ -3917,6 +3942,46 @@ void tst_QRhi::threeDimTexture()
// slice 23 is now blue
cb->endPass();
rhi->endOffscreenFrame();
// read back slice 23 (blue)
batch = rhi->nextResourceUpdateBatch();
QRhiReadbackResult readResult;
QImage result;
readResult.completed = [&readResult, &result] {
result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
readResult.pixelSize.width(), readResult.pixelSize.height(),
QImage::Format_RGBA8888);
};
QRhiReadbackDescription readbackDescription(texture.data());
readbackDescription.setLayer(23);
batch->readBackTexture(readbackDescription, &readResult);
QVERIFY(submitResourceUpdates(rhi.data(), batch));
QVERIFY(!result.isNull());
QImage referenceImage(WIDTH, HEIGHT, result.format());
referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 1.0f));
// the Null backend does not render so skip the verification for that
if (impl != QRhi::Null)
QVERIFY(imageRGBAEquals(result, referenceImage));
// read back slice 0 (black)
batch = rhi->nextResourceUpdateBatch();
result = QImage();
readbackDescription.setLayer(0);
batch->readBackTexture(readbackDescription, &readResult);
QVERIFY(submitResourceUpdates(rhi.data(), batch));
QVERIFY(!result.isNull());
referenceImage.fill(QColor::fromRgbF(0.0f, 0.0f, 0.0f));
QVERIFY(imageRGBAEquals(result, referenceImage));
// read back slice 127 (almost red)
batch = rhi->nextResourceUpdateBatch();
result = QImage();
readbackDescription.setLayer(127);
batch->readBackTexture(readbackDescription, &readResult);
QVERIFY(submitResourceUpdates(rhi.data(), batch));
QVERIFY(!result.isNull());
referenceImage.fill(QColor::fromRgb(254, 0, 0));
QVERIFY(imageRGBAEquals(result, referenceImage));
}
}