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