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:
parent
4652da536a
commit
64089b900f
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -467,6 +467,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
|
||||
QRhiTexture::Format format;
|
||||
GLenum readTarget;
|
||||
int level;
|
||||
int slice3D;
|
||||
} readPixels;
|
||||
struct {
|
||||
GLenum target;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user