rhi: support partial region readbacks in QRhiReadbackDescription
Add a `rect` property to QRhiReadbackDescription, enabling users to specify a sub-rectangle for texture or backbuffer readbacks. This is particularly useful for scenarios where full texture readbacks are unnecessary, like reading only one single pixel. [ChangeLog][RHI] Added support for specifying a sub-rectangle for readbacks in QRhiReadbackDescription. This allows partial texture or backbuffer readbacks. Change-Id: If7465e97bc90907442e2d2981826e1e26b0e4a50 Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
This commit is contained in:
parent
57d91d8029
commit
dc033758a3
@ -3467,6 +3467,22 @@ QRhiReadbackDescription::QRhiReadbackDescription(QRhiTexture *texture)
|
||||
Sets the mip \a level to read back.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn const QRect &QRhiReadbackDescription::rect() const
|
||||
\since 6.10
|
||||
|
||||
\return the rectangle to read back. Defaults to an invalid rectangle.
|
||||
|
||||
If invalid, the entire texture or swapchain backbuffer is read back.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn void QRhiReadbackDescription::setRect(const QRect &rectangle)
|
||||
\since 6.10
|
||||
|
||||
Sets the \a rectangle to read back.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\class QRhiReadbackResult
|
||||
\inmodule QtGuiPrivate
|
||||
|
@ -793,10 +793,14 @@ public:
|
||||
int level() const { return m_level; }
|
||||
void setLevel(int level) { m_level = level; }
|
||||
|
||||
QRect rect() const { return m_rect; }
|
||||
void setRect(const QRect &rectangle) { m_rect = rectangle; }
|
||||
|
||||
private:
|
||||
QRhiTexture *m_texture = nullptr;
|
||||
int m_layer = 0;
|
||||
int m_level = 0;
|
||||
QRect m_rect;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(QRhiReadbackDescription, Q_RELOCATABLE_TYPE);
|
||||
|
@ -1990,7 +1990,7 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
|
||||
ID3D11Resource *src;
|
||||
DXGI_FORMAT dxgiFormat;
|
||||
QSize pixelSize;
|
||||
QRect rect;
|
||||
QRhiTexture::Format format;
|
||||
UINT subres = 0;
|
||||
QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.rb.texture());
|
||||
@ -2004,7 +2004,10 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
}
|
||||
src = texD->textureResource();
|
||||
dxgiFormat = texD->dxgiFormat;
|
||||
pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
if (u.rb.rect().isValid())
|
||||
rect = u.rb.rect();
|
||||
else
|
||||
rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
|
||||
format = texD->m_format;
|
||||
is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
|
||||
subres = D3D11CalcSubresource(UINT(u.rb.level()), UINT(is3D ? 0 : u.rb.layer()), texD->mipLevelCount);
|
||||
@ -2024,18 +2027,21 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
}
|
||||
src = swapChainD->backBufferTex;
|
||||
dxgiFormat = swapChainD->colorFormat;
|
||||
pixelSize = swapChainD->pixelSize;
|
||||
if (u.rb.rect().isValid())
|
||||
rect = u.rb.rect();
|
||||
else
|
||||
rect = QRect({0, 0}, swapChainD->pixelSize);
|
||||
format = swapchainReadbackTextureFormat(dxgiFormat, nullptr);
|
||||
if (format == QRhiTexture::UnknownFormat)
|
||||
continue;
|
||||
}
|
||||
quint32 byteSize = 0;
|
||||
quint32 bpl = 0;
|
||||
textureFormatInfo(format, pixelSize, &bpl, &byteSize, nullptr);
|
||||
textureFormatInfo(format, rect.size(), &bpl, &byteSize, nullptr);
|
||||
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
desc.Width = UINT(pixelSize.width());
|
||||
desc.Height = UINT(pixelSize.height());
|
||||
desc.Width = UINT(rect.width());
|
||||
desc.Height = UINT(rect.height());
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = dxgiFormat;
|
||||
@ -2059,22 +2065,22 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
cmd.args.copySubRes.dstZ = 0;
|
||||
cmd.args.copySubRes.src = src;
|
||||
cmd.args.copySubRes.srcSubRes = subres;
|
||||
if (is3D) {
|
||||
D3D11_BOX 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;
|
||||
}
|
||||
|
||||
D3D11_BOX srcBox = {};
|
||||
srcBox.left = UINT(rect.left());
|
||||
srcBox.top = UINT(rect.top());
|
||||
srcBox.front = is3D ? UINT(u.rb.layer()) : 0u;
|
||||
// back, right, bottom are exclusive
|
||||
srcBox.right = srcBox.left + desc.Width;
|
||||
srcBox.bottom = srcBox.top + desc.Height;
|
||||
srcBox.back = srcBox.front + 1;
|
||||
cmd.args.copySubRes.hasSrcBox = true;
|
||||
cmd.args.copySubRes.srcBox = srcBox;
|
||||
|
||||
readback.stagingTex = stagingTex;
|
||||
readback.byteSize = byteSize;
|
||||
readback.bpl = bpl;
|
||||
readback.pixelSize = pixelSize;
|
||||
readback.pixelSize = rect.size();
|
||||
readback.format = format;
|
||||
|
||||
activeTextureReadbacks.append(readback);
|
||||
|
@ -3703,6 +3703,7 @@ void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpd
|
||||
readback.result = u.result;
|
||||
|
||||
QD3D12ObjectHandle srcHandle;
|
||||
QRect rect;
|
||||
bool is3D = false;
|
||||
if (u.rb.texture()) {
|
||||
QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.rb.texture());
|
||||
@ -3711,17 +3712,24 @@ void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpd
|
||||
continue;
|
||||
}
|
||||
is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
|
||||
readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
if (u.rb.rect().isValid())
|
||||
rect = u.rb.rect();
|
||||
else
|
||||
rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
|
||||
readback.format = texD->m_format;
|
||||
srcHandle = texD->handle;
|
||||
} else {
|
||||
Q_ASSERT(currentSwapChain);
|
||||
readback.pixelSize = currentSwapChain->pixelSize;
|
||||
if (u.rb.rect().isValid())
|
||||
rect = u.rb.rect();
|
||||
else
|
||||
rect = QRect({0, 0}, currentSwapChain->pixelSize);
|
||||
readback.format = swapchainReadbackTextureFormat(currentSwapChain->colorFormat, nullptr);
|
||||
if (readback.format == QRhiTexture::UnknownFormat)
|
||||
continue;
|
||||
srcHandle = currentSwapChain->colorBuffers[currentSwapChain->currentBackBufferIndex];
|
||||
}
|
||||
readback.pixelSize = rect.size();
|
||||
|
||||
textureFormatInfo(readback.format,
|
||||
readback.pixelSize,
|
||||
@ -3774,13 +3782,15 @@ void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpd
|
||||
src.SubresourceIndex = subresource;
|
||||
|
||||
D3D12_BOX srcBox = {};
|
||||
if (is3D) {
|
||||
srcBox.front = UINT(u.rb.layer());
|
||||
srcBox.back = srcBox.front + 1;
|
||||
srcBox.right = readback.pixelSize.width(); // exclusive
|
||||
srcBox.bottom = readback.pixelSize.height();
|
||||
}
|
||||
cbD->cmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, is3D ? &srcBox : nullptr);
|
||||
srcBox.left = UINT(rect.left());
|
||||
srcBox.top = UINT(rect.top());
|
||||
srcBox.front = is3D ? UINT(u.rb.layer()) : 0u;
|
||||
// back, right, bottom are exclusive
|
||||
srcBox.right = srcBox.left + UINT(rect.width());
|
||||
srcBox.bottom = srcBox.top + UINT(rect.height());
|
||||
srcBox.back = srcBox.front + 1;
|
||||
|
||||
cbD->cmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, &srcBox);
|
||||
activeReadbacks.append(readback);
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
|
||||
QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.dst);
|
||||
|
@ -2759,9 +2759,19 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
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();
|
||||
if (u.rb.rect().isValid()) {
|
||||
cmd.args.readPixels.x = u.rb.rect().x();
|
||||
cmd.args.readPixels.y = u.rb.rect().y();
|
||||
cmd.args.readPixels.w = u.rb.rect().width();
|
||||
cmd.args.readPixels.h = u.rb.rect().height();
|
||||
}
|
||||
else {
|
||||
const QSize readImageSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
cmd.args.readPixels.x = 0;
|
||||
cmd.args.readPixels.y = 0;
|
||||
cmd.args.readPixels.w = readImageSize.width();
|
||||
cmd.args.readPixels.h = readImageSize.height();
|
||||
}
|
||||
cmd.args.readPixels.format = texD->m_format;
|
||||
if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional)
|
||||
|| texD->m_flags.testFlag(QRhiTexture::TextureArray))
|
||||
@ -2775,6 +2785,20 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
}
|
||||
cmd.args.readPixels.level = u.rb.level();
|
||||
}
|
||||
else { // swapchain
|
||||
if (u.rb.rect().isValid()) {
|
||||
cmd.args.readPixels.x = u.rb.rect().x();
|
||||
cmd.args.readPixels.y = u.rb.rect().y();
|
||||
cmd.args.readPixels.w = u.rb.rect().width();
|
||||
cmd.args.readPixels.h = u.rb.rect().height();
|
||||
}
|
||||
else {
|
||||
cmd.args.readPixels.x = 0;
|
||||
cmd.args.readPixels.y = 0;
|
||||
cmd.args.readPixels.w = currentSwapChain->pixelSize.width();
|
||||
cmd.args.readPixels.h = currentSwapChain->pixelSize.height();
|
||||
}
|
||||
}
|
||||
} else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
|
||||
QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst);
|
||||
trackedImageBarrier(cbD, texD, QGles2Texture::AccessFramebuffer);
|
||||
@ -3600,8 +3624,8 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
|
||||
GLuint tex = cmd.args.readPixels.texture;
|
||||
GLuint fbo = 0;
|
||||
int mipLevel = 0;
|
||||
result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h);
|
||||
if (tex) {
|
||||
result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h);
|
||||
result->format = cmd.args.readPixels.format;
|
||||
mipLevel = cmd.args.readPixels.level;
|
||||
if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
|
||||
@ -3619,12 +3643,13 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result->pixelSize = currentSwapChain->pixelSize;
|
||||
result->format = QRhiTexture::RGBA8;
|
||||
// readPixels handles multisample resolving implicitly
|
||||
}
|
||||
const int w = result->pixelSize.width();
|
||||
const int h = result->pixelSize.height();
|
||||
const int x = cmd.args.readPixels.x;
|
||||
const int y = cmd.args.readPixels.y;
|
||||
const int w = cmd.args.readPixels.w;
|
||||
const int h = cmd.args.readPixels.h;
|
||||
if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) {
|
||||
// With GLES, GL_RGBA is the only mandated readback format, so stick with it.
|
||||
// (and that's why we return false for the ReadBackAnyTextureFormat feature)
|
||||
@ -3632,7 +3657,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
|
||||
result->data.resize(w * h);
|
||||
QByteArray tmpBuf;
|
||||
tmpBuf.resize(w * h * 4);
|
||||
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, tmpBuf.data());
|
||||
f->glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, tmpBuf.data());
|
||||
const quint8 *srcBase = reinterpret_cast<const quint8 *>(tmpBuf.constData());
|
||||
quint8 *dstBase = reinterpret_cast<quint8 *>(result->data.data());
|
||||
const int componentIndex = isFeatureSupported(QRhi::RedOrAlpha8IsRed) ? 0 : 3;
|
||||
@ -3652,27 +3677,27 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
|
||||
// not, there's nothing we can do.
|
||||
case QRhiTexture::RGBA16F:
|
||||
result->data.resize(w * h * 8);
|
||||
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_HALF_FLOAT, result->data.data());
|
||||
f->glReadPixels(x, y, w, h, GL_RGBA, GL_HALF_FLOAT, result->data.data());
|
||||
break;
|
||||
case QRhiTexture::R16F:
|
||||
result->data.resize(w * h * 2);
|
||||
f->glReadPixels(0, 0, w, h, GL_RED, GL_HALF_FLOAT, result->data.data());
|
||||
f->glReadPixels(x, y, w, h, GL_RED, GL_HALF_FLOAT, result->data.data());
|
||||
break;
|
||||
case QRhiTexture::R32F:
|
||||
result->data.resize(w * h * 4);
|
||||
f->glReadPixels(0, 0, w, h, GL_RED, GL_FLOAT, result->data.data());
|
||||
f->glReadPixels(x, y, w, h, GL_RED, GL_FLOAT, result->data.data());
|
||||
break;
|
||||
case QRhiTexture::RGBA32F:
|
||||
result->data.resize(w * h * 16);
|
||||
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_FLOAT, result->data.data());
|
||||
f->glReadPixels(x, y, w, h, GL_RGBA, GL_FLOAT, result->data.data());
|
||||
break;
|
||||
case QRhiTexture::RGB10A2:
|
||||
result->data.resize(w * h * 4);
|
||||
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, result->data.data());
|
||||
f->glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, result->data.data());
|
||||
break;
|
||||
default:
|
||||
result->data.resize(w * h * 4);
|
||||
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, result->data.data());
|
||||
f->glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, result->data.data());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -453,6 +453,8 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
|
||||
struct {
|
||||
QRhiReadbackResult *result;
|
||||
GLuint texture;
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
QRhiTexture::Format format;
|
||||
|
@ -2914,7 +2914,7 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture());
|
||||
QMetalSwapChain *swapChainD = nullptr;
|
||||
id<MTLTexture> src;
|
||||
QSize srcSize;
|
||||
QRect rect;
|
||||
bool is3D = false;
|
||||
if (texD) {
|
||||
if (texD->samples > 1) {
|
||||
@ -2922,22 +2922,27 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
continue;
|
||||
}
|
||||
is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
|
||||
readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
if (u.rb.rect().isValid())
|
||||
rect = u.rb.rect();
|
||||
else
|
||||
rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
|
||||
readback.format = texD->m_format;
|
||||
src = texD->d->tex;
|
||||
srcSize = readback.pixelSize;
|
||||
texD->lastActiveFrameSlot = currentFrameSlot;
|
||||
} else {
|
||||
Q_ASSERT(currentSwapChain);
|
||||
swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
|
||||
readback.pixelSize = swapChainD->pixelSize;
|
||||
if (u.rb.rect().isValid())
|
||||
rect = u.rb.rect();
|
||||
else
|
||||
rect = QRect({0, 0}, swapChainD->pixelSize);
|
||||
readback.format = swapChainD->d->rhiColorFormat;
|
||||
// Multisample swapchains need nothing special since resolving
|
||||
// happens when ending a renderpass.
|
||||
const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
|
||||
src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
|
||||
srcSize = swapChainD->rtWrapper.d->pixelSize;
|
||||
}
|
||||
readback.pixelSize = rect.size();
|
||||
|
||||
quint32 bpl = 0;
|
||||
textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize, nullptr);
|
||||
@ -2947,8 +2952,8 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
|
||||
[blitEnc copyFromTexture: src
|
||||
sourceSlice: NSUInteger(is3D ? 0 : u.rb.layer())
|
||||
sourceLevel: NSUInteger(u.rb.level())
|
||||
sourceOrigin: MTLOriginMake(0, 0, is3D ? u.rb.layer() : 0)
|
||||
sourceSize: MTLSizeMake(NSUInteger(srcSize.width()), NSUInteger(srcSize.height()), 1)
|
||||
sourceOrigin: MTLOriginMake(NSUInteger(rect.x()), NSUInteger(rect.y()), NSUInteger(is3D ? u.rb.layer() : 0))
|
||||
sourceSize: MTLSizeMake(NSUInteger(rect.width()), NSUInteger(rect.height()), 1)
|
||||
toBuffer: readback.buf
|
||||
destinationOffset: 0
|
||||
destinationBytesPerRow: bpl
|
||||
|
@ -515,11 +515,17 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re
|
||||
QNullTexture *texD = QRHI_RES(QNullTexture, u.rb.texture());
|
||||
if (texD) {
|
||||
result->format = texD->format();
|
||||
result->pixelSize = q->sizeForMipLevel(u.rb.level(), texD->pixelSize());
|
||||
if (u.rb.rect().isValid())
|
||||
result->pixelSize = u.rb.rect().size();
|
||||
else
|
||||
result->pixelSize = q->sizeForMipLevel(u.rb.level(), texD->pixelSize());
|
||||
} else {
|
||||
Q_ASSERT(currentSwapChain);
|
||||
result->format = QRhiTexture::RGBA8;
|
||||
result->pixelSize = currentSwapChain->currentPixelSize();
|
||||
if (u.rb.rect().isValid())
|
||||
result->pixelSize = u.rb.rect().size();
|
||||
else
|
||||
result->pixelSize = currentSwapChain->currentPixelSize();
|
||||
}
|
||||
quint32 bytesPerLine = 0;
|
||||
quint32 byteSize = 0;
|
||||
|
@ -4321,7 +4321,10 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
continue;
|
||||
}
|
||||
is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
|
||||
readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
|
||||
if (u.rb.rect().isValid())
|
||||
readback.rect = u.rb.rect();
|
||||
else
|
||||
readback.rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
|
||||
readback.format = texD->m_format;
|
||||
texD->lastActiveFrameSlot = currentFrameSlot;
|
||||
} else {
|
||||
@ -4331,7 +4334,10 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
qWarning("Swapchain does not support readback");
|
||||
continue;
|
||||
}
|
||||
readback.pixelSize = swapChainD->pixelSize;
|
||||
if (u.rb.rect().isValid())
|
||||
readback.rect = u.rb.rect();
|
||||
else
|
||||
readback.rect = QRect({0, 0}, swapChainD->pixelSize);
|
||||
readback.format = swapchainReadbackTextureFormat(swapChainD->colorFormat, nullptr);
|
||||
if (readback.format == QRhiTexture::UnknownFormat)
|
||||
continue;
|
||||
@ -4339,7 +4345,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
// Multisample swapchains need nothing special since resolving
|
||||
// happens when ending a renderpass.
|
||||
}
|
||||
textureFormatInfo(readback.format, readback.pixelSize, nullptr, &readback.byteSize, nullptr);
|
||||
textureFormatInfo(readback.format, readback.rect.size(), nullptr, &readback.byteSize, nullptr);
|
||||
|
||||
// Create a host visible readback buffer.
|
||||
VkBufferCreateInfo bufferInfo = {};
|
||||
@ -4367,10 +4373,12 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
|
||||
copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level());
|
||||
copyDesc.imageSubresource.baseArrayLayer = is3D ? 0 : uint32_t(u.rb.layer());
|
||||
copyDesc.imageSubresource.layerCount = 1;
|
||||
copyDesc.imageOffset.x = readback.rect.x();
|
||||
copyDesc.imageOffset.y = readback.rect.y();
|
||||
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.width = uint32_t(readback.rect.width());
|
||||
copyDesc.imageExtent.height = uint32_t(readback.rect.height());
|
||||
copyDesc.imageExtent.depth = 1;
|
||||
|
||||
if (texD) {
|
||||
@ -4634,7 +4642,7 @@ void QRhiVulkan::finishActiveReadbacks(bool forced)
|
||||
const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]);
|
||||
if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) {
|
||||
readback.result->format = readback.format;
|
||||
readback.result->pixelSize = readback.pixelSize;
|
||||
readback.result->pixelSize = readback.rect.size();
|
||||
VmaAllocation a = toVmaAllocation(readback.stagingAlloc);
|
||||
void *p = nullptr;
|
||||
VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
|
||||
|
@ -999,7 +999,7 @@ public:
|
||||
VkBuffer stagingBuf;
|
||||
QVkAlloc stagingAlloc;
|
||||
quint32 byteSize;
|
||||
QSize pixelSize;
|
||||
QRect rect;
|
||||
QRhiTexture::Format format;
|
||||
};
|
||||
QVarLengthArray<TextureReadback, 2> activeTextureReadbacks;
|
||||
|
@ -4090,11 +4090,16 @@ void tst_QRhi::renderToWindowSimple()
|
||||
const int asyncReadbackFrames = rhi->resourceLimit(QRhi::MaxAsyncReadbackFrames);
|
||||
// one frame issues the readback, then we do MaxAsyncReadbackFrames more to ensure the readback completes
|
||||
const int FRAME_COUNT = asyncReadbackFrames + 1;
|
||||
|
||||
bool readCompleted = false;
|
||||
QRhiReadbackResult readResult;
|
||||
QImage result;
|
||||
int readbackWidth = 0;
|
||||
|
||||
bool readCompletedPartial = false;
|
||||
QRhiReadbackResult readResultPartial;
|
||||
QImage resultPartial;
|
||||
|
||||
for (int frameNo = 0; frameNo < FRAME_COUNT; ++frameNo) {
|
||||
QVERIFY(rhi->beginFrame(swapChain.data()) == QRhi::FrameOpSuccess);
|
||||
QRhiCommandBuffer *cb = swapChain->currentFrameCommandBuffer();
|
||||
@ -4115,6 +4120,7 @@ void tst_QRhi::renderToWindowSimple()
|
||||
cb->draw(3);
|
||||
|
||||
if (frameNo == 0) {
|
||||
QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
|
||||
readResult.completed = [&readCompleted, &readResult, &result, &rhi] {
|
||||
readCompleted = true;
|
||||
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
|
||||
@ -4127,9 +4133,26 @@ void tst_QRhi::renderToWindowSimple()
|
||||
else
|
||||
result = wrapperImage.copy();
|
||||
};
|
||||
QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
|
||||
readbackBatch->readBackTexture({}, &readResult); // read back the current backbuffer
|
||||
QRhiReadbackDescription readbackDescription;
|
||||
QVERIFY(!readbackDescription.rect().isValid());
|
||||
readbackBatch->readBackTexture(readbackDescription, &readResult); // read back the current backbuffer
|
||||
readbackWidth = outputSize.width();
|
||||
readResultPartial.completed = [&readCompletedPartial, &readResultPartial, &resultPartial, &rhi] {
|
||||
readCompletedPartial = true;
|
||||
QImage wrapperImage(reinterpret_cast<const uchar *>(readResultPartial.data.constData()),
|
||||
readResultPartial.pixelSize.width(), readResultPartial.pixelSize.height(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
if (readResultPartial.format == QRhiTexture::RGBA8)
|
||||
wrapperImage = wrapperImage.rgbSwapped();
|
||||
if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC())
|
||||
resultPartial = wrapperImage.flipped();
|
||||
else
|
||||
resultPartial = wrapperImage.copy();
|
||||
};
|
||||
QRhiReadbackDescription partialReadbackDescription;
|
||||
partialReadbackDescription.setRect({100, 100, 1, 1});
|
||||
QVERIFY(partialReadbackDescription.rect().isValid());
|
||||
readbackBatch->readBackTexture(partialReadbackDescription, &readResultPartial); // read back one pixel at 100,100 of the current backbuffer
|
||||
cb->endPass(readbackBatch);
|
||||
} else {
|
||||
cb->endPass();
|
||||
@ -4166,6 +4189,13 @@ void tst_QRhi::renderToWindowSimple()
|
||||
|
||||
QCOMPARE(redCount + blueCount, readbackWidth);
|
||||
QVERIFY(redCount < blueCount);
|
||||
|
||||
// Verify the backbuffer single-pixel readback
|
||||
QVERIFY(readCompletedPartial);
|
||||
QCOMPARE(readResultPartial.pixelSize, QSize(1, 1));
|
||||
if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC())
|
||||
result.flip();
|
||||
QCOMPARE(resultPartial.pixelColor(0, 0), result.pixelColor(100, 100));
|
||||
}
|
||||
|
||||
void tst_QRhi::continuousReadbackFromWindow_data()
|
||||
|
Loading…
x
Reference in New Issue
Block a user