summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2021-02-03 18:07:47 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2021-03-30 16:43:35 +0200
commit7ccd2d02463f8076631bc45e5bf1386c61037dc7 (patch)
tree02614d53f737f1c4aa56549221262d43e3cbc2fc
parent5ce367a55224c862c11aaf5945ddf7bd3c934600 (diff)
rhi: Add support for custom bytes-per-line for uncompressed raw data
Fixes: QTBUG-90770 Change-Id: Icba328c417bcce256e7b44f1d540af7f8e83376b Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Christian Strømme <christian.stromme@qt.io>
-rw-r--r--src/gui/rhi/qrhi.cpp33
-rw-r--r--src/gui/rhi/qrhi_p.h7
-rw-r--r--src/gui/rhi/qrhi_p_p.h2
-rw-r--r--src/gui/rhi/qrhid3d11.cpp9
-rw-r--r--src/gui/rhi/qrhigles2.cpp22
-rw-r--r--src/gui/rhi/qrhigles2_p_p.h1
-rw-r--r--src/gui/rhi/qrhimetal.mm10
-rw-r--r--src/gui/rhi/qrhinull.cpp7
-rw-r--r--src/gui/rhi/qrhivulkan.cpp10
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp63
10 files changed, 144 insertions, 20 deletions
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index db85730040..fea688e9cd 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -636,6 +636,12 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
functions will not perform any action, the retrieved blob is always empty,
and thus no benefits can be expected from retrieving and, during a
subsequent run of the application, reloading the pipeline cache content.
+
+ \value ImageDataStride Indicates that specifying a custom stride (row
+ length) for raw image data in texture uploads is supported. When not
+ supported (which can happen when the underlying API is OpenGL ES 2.0 without
+ support for GL_UNPACK_ROW_LENGTH),
+ QRhiTextureSubresourceUploadDescription::setDataStride() must not be used.
*/
/*!
@@ -1585,16 +1591,28 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh
\note Setting sourceSize() or sourceTopLeft() may trigger a QImage copy
internally, depending on the format and the backend.
- When providing raw data, the stride (row pitch, row length in bytes) of the
+ When providing raw data, and the stride is not specified via
+ setDataStride(), the stride (row pitch, row length in bytes) of the
provided data must be equal to \c{width * pixelSize} where \c pixelSize is
the number of bytes used for one pixel, and there must be no additional
padding between rows. There is no row start alignment requirement.
+ When there is unused data at the end of each row in the input raw data,
+ call setDataStride() with the total number of bytes per row. The stride
+ must always be a multiple of the number of bytes for one pixel. The row
+ stride is only applicable to image data for textures with an uncompressed
+ format.
+
\note The format of the source data must be compatible with the texture
format. With many graphics APIs the data is copied as-is into a staging
buffer, there is no intermediate format conversion provided by QRhi. This
applies to floating point formats as well, with, for example, RGBA16F
requiring half floats in the source data.
+
+ \note Setting the stride via setDataStride() is only functional when
+ QRhi::ImageDataStride is reported as
+ \l{QRhi::isFeatureSupported()}{supported}. In practice this can be expected
+ to be supported everywhere except for OpenGL ES 2.0.
*/
/*!
@@ -1637,11 +1655,10 @@ QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription
}
/*!
- Constructs a mip level description with the image data specified by \a data. This is suitable
- for floating point and compressed formats as well.
+ Constructs a mip level description with the image data specified by \a
+ data. This is suitable for floating point and compressed formats as well.
*/
-QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription(
- const QByteArray &data)
+QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription(const QByteArray &data)
: m_data(data)
{
}
@@ -4492,7 +4509,7 @@ void QRhiImplementation::compressedFormatInfo(QRhiTexture::Format format, const
}
void QRhiImplementation::textureFormatInfo(QRhiTexture::Format format, const QSize &size,
- quint32 *bpl, quint32 *byteSize) const
+ quint32 *bpl, quint32 *byteSize, quint32 *bytesPerPixel) const
{
if (isCompressedFormat(format)) {
compressedFormatInfo(format, size, bpl, byteSize, nullptr);
@@ -4551,6 +4568,8 @@ void QRhiImplementation::textureFormatInfo(QRhiTexture::Format format, const QSi
*bpl = uint(size.width()) * bpc;
if (byteSize)
*byteSize = uint(size.width() * size.height()) * bpc;
+ if (bytesPerPixel)
+ *bytesPerPixel = bpc;
}
// Approximate because it excludes subresource alignment or multisampling.
@@ -4562,7 +4581,7 @@ quint32 QRhiImplementation::approxByteSizeForTexture(QRhiTexture::Format format,
quint32 byteSize = 0;
const QSize size(qFloor(qreal(qMax(1, baseSize.width() >> level))),
qFloor(qreal(qMax(1, baseSize.height() >> level))));
- textureFormatInfo(format, size, nullptr, &byteSize);
+ textureFormatInfo(format, size, nullptr, &byteSize, nullptr);
approxSize += byteSize;
}
approxSize *= uint(layerCount);
diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h
index e0afabc73a..a4d65a661a 100644
--- a/src/gui/rhi/qrhi_p.h
+++ b/src/gui/rhi/qrhi_p.h
@@ -518,6 +518,9 @@ public:
QByteArray data() const { return m_data; }
void setData(const QByteArray &data) { m_data = data; }
+ quint32 dataStride() const { return m_dataStride; }
+ void setDataStride(quint32 stride) { m_dataStride = stride; }
+
QPoint destinationTopLeft() const { return m_destinationTopLeft; }
void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; }
@@ -530,6 +533,7 @@ public:
private:
QImage m_image;
QByteArray m_data;
+ quint32 m_dataStride = 0;
QPoint m_destinationTopLeft;
QSize m_sourceSize;
QPoint m_sourceTopLeft;
@@ -1528,7 +1532,8 @@ public:
IntAttributes,
ScreenSpaceDerivatives,
ReadBackAnyTextureFormat,
- PipelineCacheDataLoadSave
+ PipelineCacheDataLoadSave,
+ ImageDataStride
};
enum BeginFrameFlag {
diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h
index fc9c99af7e..f33626ae1d 100644
--- a/src/gui/rhi/qrhi_p_p.h
+++ b/src/gui/rhi/qrhi_p_p.h
@@ -179,7 +179,7 @@ public:
quint32 *bpl, quint32 *byteSize,
QSize *blockDim) const;
void textureFormatInfo(QRhiTexture::Format format, const QSize &size,
- quint32 *bpl, quint32 *byteSize) const;
+ quint32 *bpl, quint32 *byteSize, quint32 *bytesPerPixel) const;
quint32 approxByteSizeForTexture(QRhiTexture::Format format, const QSize &baseSize,
int mipCount, int layerCount);
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index 3e0ba0820b..addb058d4d 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -542,6 +542,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::PipelineCacheDataLoadSave:
return false;
+ case QRhi::ImageDataStride:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -1401,7 +1403,10 @@ void QRhiD3D11::enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cb
const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
: subresDesc.sourceSize();
quint32 bpl = 0;
- textureFormatInfo(texD->m_format, size, &bpl, nullptr);
+ if (subresDesc.dataStride())
+ bpl = subresDesc.dataStride();
+ else
+ textureFormatInfo(texD->m_format, size, &bpl, nullptr, nullptr);
box.left = UINT(dp.x());
box.top = UINT(dp.y());
box.right = UINT(dp.x() + size.width());
@@ -1577,7 +1582,7 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
}
quint32 byteSize = 0;
quint32 bpl = 0;
- textureFormatInfo(format, pixelSize, &bpl, &byteSize);
+ textureFormatInfo(format, pixelSize, &bpl, &byteSize, nullptr);
D3D11_TEXTURE2D_DESC desc;
memset(&desc, 0, sizeof(desc));
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index e3895897e9..c2035869c3 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -340,6 +340,10 @@ QT_BEGIN_NAMESPACE
#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
#endif
+#ifndef GL_UNPACK_ROW_LENGTH
+#define GL_UNPACK_ROW_LENGTH 0x0CF2
+#endif
+
/*!
Constructs a new QRhiGles2InitParams.
@@ -992,6 +996,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return false;
case QRhi::PipelineCacheDataLoadSave:
return caps.programBinary;
+ case QRhi::ImageDataStride:
+ return !caps.gles || caps.ctxMajor >= 3;
default:
Q_UNREACHABLE();
return false;
@@ -1796,6 +1802,7 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
cmd.args.subImage.glformat = texD->glformat;
cmd.args.subImage.gltype = texD->gltype;
cmd.args.subImage.rowStartAlign = 4;
+ cmd.args.subImage.rowLength = 0;
cmd.args.subImage.data = cbD->retainImage(img);
} else if (!rawData.isEmpty() && isCompressed) {
if (!texD->compressedAtlasBuilt && (texD->flags() & QRhiTexture::UsedAsCompressedAtlas)) {
@@ -1849,8 +1856,9 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
} else if (!rawData.isEmpty()) {
const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
: subresDesc.sourceSize();
- quint32 bpl = 0;
- textureFormatInfo(texD->m_format, size, &bpl, nullptr);
+ quint32 bytesPerLine = 0;
+ quint32 bytesPerPixel = 0;
+ textureFormatInfo(texD->m_format, size, &bytesPerLine, nullptr, &bytesPerPixel);
QGles2CommandBuffer::Command &cmd(cbD->commands.get());
cmd.cmd = QGles2CommandBuffer::Command::SubImage;
cmd.args.subImage.target = texD->target;
@@ -1866,7 +1874,11 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
// Default unpack alignment (row start aligment
// requirement) is 4. QImage guarantees 4 byte aligned
// row starts, but our raw data here does not.
- cmd.args.subImage.rowStartAlign = (bpl & 3) ? 1 : 4;
+ cmd.args.subImage.rowStartAlign = (bytesPerLine & 3) ? 1 : 4;
+ if (subresDesc.dataStride() && bytesPerPixel)
+ cmd.args.subImage.rowLength = subresDesc.dataStride() / bytesPerPixel;
+ else
+ cmd.args.subImage.rowLength = 0;
cmd.args.subImage.data = cbD->retainData(rawData);
} else {
qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
@@ -2796,6 +2808,8 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
f->glBindTexture(cmd.args.subImage.target, cmd.args.subImage.texture);
if (cmd.args.subImage.rowStartAlign != 4)
f->glPixelStorei(GL_UNPACK_ALIGNMENT, cmd.args.subImage.rowStartAlign);
+ if (cmd.args.subImage.rowLength != 0)
+ f->glPixelStorei(GL_UNPACK_ROW_LENGTH, cmd.args.subImage.rowLength);
f->glTexSubImage2D(cmd.args.subImage.faceTarget, cmd.args.subImage.level,
cmd.args.subImage.dx, cmd.args.subImage.dy,
cmd.args.subImage.w, cmd.args.subImage.h,
@@ -2803,6 +2817,8 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
cmd.args.subImage.data);
if (cmd.args.subImage.rowStartAlign != 4)
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ if (cmd.args.subImage.rowLength != 0)
+ f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
break;
case QGles2CommandBuffer::Command::CompressedImage:
f->glBindTexture(cmd.args.compressedImage.target, cmd.args.compressedImage.texture);
diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h
index d30fd89545..67a21f3053 100644
--- a/src/gui/rhi/qrhigles2_p_p.h
+++ b/src/gui/rhi/qrhigles2_p_p.h
@@ -473,6 +473,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
GLenum glformat;
GLenum gltype;
int rowStartAlign;
+ int rowLength;
const void *data; // must come from retainImage()
} subImage;
struct {
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index ddbd0ea740..ab255f74df 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -593,6 +593,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::PipelineCacheDataLoadSave:
return false;
+ case QRhi::ImageDataStride:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -1703,7 +1705,11 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc
}
quint32 bpl = 0;
- textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr);
+ if (subresDesc.dataStride())
+ bpl = subresDesc.dataStride();
+ else
+ textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, nullptr);
+
memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), size_t(rawData.size()));
[blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
@@ -1860,7 +1866,7 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
}
quint32 bpl = 0;
- textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize);
+ textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize, nullptr);
readback.buf = [d->dev newBufferWithLength: readback.bufSize options: MTLResourceStorageModeShared];
QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(readback.buf)),
diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp
index 29f3bab8b0..0a25b58b91 100644
--- a/src/gui/rhi/qrhinull.cpp
+++ b/src/gui/rhi/qrhinull.cpp
@@ -437,12 +437,15 @@ void QRhiNull::simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::Textu
// sourceTopLeft is not supported on this path as per QRhi docs
const char *src = subresDesc.data().constData();
const int srcBpl = w * 4;
+ int srcStride = srcBpl;
+ if (subresDesc.dataStride())
+ srcStride = subresDesc.dataStride();
const QPoint dstOffset = subresDesc.destinationTopLeft();
uchar *dst = texD->image[layer][level].bits();
const int dstBpl = texD->image[layer][level].bytesPerLine();
for (int y = 0; y < h; ++y) {
memcpy(dst + dstOffset.x() * 4 + (y + dstOffset.y()) * dstBpl,
- src + y * srcBpl,
+ src + y * srcStride,
size_t(srcBpl));
}
}
@@ -516,7 +519,7 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re
}
quint32 bytesPerLine = 0;
quint32 byteSize = 0;
- textureFormatInfo(result->format, result->pixelSize, &bytesPerLine, &byteSize);
+ textureFormatInfo(result->format, result->pixelSize, &bytesPerLine, &byteSize, nullptr);
if (texD && texD->format() == QRhiTexture::RGBA8) {
result->data.resize(int(byteSize));
const QImage &src(texD->image[u.rb.layer()][u.rb.level()]);
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index f6b1654acd..26d6de498b 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -3012,6 +3012,12 @@ void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level,
copySizeBytes = imageSizeBytes = rawData.size();
src = rawData.constData();
QSize size = q->sizeForMipLevel(level, texD->m_pixelSize);
+ if (subresDesc.dataStride()) {
+ quint32 bytesPerPixel = 0;
+ textureFormatInfo(texD->m_format, size, nullptr, nullptr, &bytesPerPixel);
+ if (bytesPerPixel)
+ copyInfo.bufferRowLength = subresDesc.dataStride() / bytesPerPixel;
+ }
if (!subresDesc.sourceSize().isEmpty())
size = subresDesc.sourceSize();
copyInfo.imageOffset.x = dp.x();
@@ -3350,7 +3356,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);
+ textureFormatInfo(readback.format, readback.pixelSize, nullptr, &readback.byteSize, nullptr);
// Create a host visible readback buffer.
VkBufferCreateInfo bufferInfo;
@@ -4231,6 +4237,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::PipelineCacheDataLoadSave:
return true;
+ case QRhi::ImageDataStride:
+ return true;
default:
Q_UNREACHABLE();
return false;
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index c4d1dca1bb..6d0ec2a488 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -89,6 +89,8 @@ private slots:
void resourceUpdateBatchRGBATextureCopy();
void resourceUpdateBatchRGBATextureMip_data();
void resourceUpdateBatchRGBATextureMip();
+ void resourceUpdateBatchTextureRawDataStride_data();
+ void resourceUpdateBatchTextureRawDataStride();
void invalidPipeline_data();
void invalidPipeline();
void srbLayoutCompatibility_data();
@@ -348,7 +350,8 @@ void tst_QRhi::create()
QRhi::IntAttributes,
QRhi::ScreenSpaceDerivatives,
QRhi::ReadBackAnyTextureFormat,
- QRhi::PipelineCacheDataLoadSave
+ QRhi::PipelineCacheDataLoadSave,
+ QRhi::ImageDataStride
};
for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->isFeatureSupported(features[i]);
@@ -1293,6 +1296,64 @@ void tst_QRhi::resourceUpdateBatchRGBATextureMip()
}
}
+void tst_QRhi::resourceUpdateBatchTextureRawDataStride_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::resourceUpdateBatchTextureRawDataStride()
+{
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing texture resource updates");
+
+ const int WIDTH = 150;
+ const int DATA_WIDTH = 180;
+ const int HEIGHT = 50;
+ QByteArray image;
+ image.resize(DATA_WIDTH * HEIGHT * 4);
+ for (int y = 0; y < HEIGHT; ++y) {
+ char *p = image.data() + y * DATA_WIDTH * 4;
+ memset(p, y, DATA_WIDTH * 4);
+ }
+
+ {
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(WIDTH, HEIGHT),
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+
+ QRhiTextureSubresourceUploadDescription subresDesc(image.constData(), image.size());
+ subresDesc.setDataStride(DATA_WIDTH * 4);
+ QRhiTextureUploadEntry upload(0, 0, subresDesc);
+ QRhiTextureUploadDescription uploadDesc(upload);
+ batch->uploadTexture(texture.data(), uploadDesc);
+
+ QRhiReadbackResult readResult;
+ bool readCompleted = false;
+ readResult.completed = [&readCompleted] { readCompleted = true; };
+ batch->readBackTexture(texture.data(), &readResult);
+
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(readCompleted);
+ QCOMPARE(readResult.format, QRhiTexture::RGBA8);
+ QCOMPARE(readResult.pixelSize, QSize(WIDTH, HEIGHT));
+
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied);
+ // wrap the original data, note the bytesPerLine argument
+ QImage originalWrapperImage(reinterpret_cast<const uchar *>(image.constData()),
+ WIDTH, HEIGHT, DATA_WIDTH * 4,
+ QImage::Format_RGBA8888_Premultiplied);
+ QVERIFY(imageRGBAEquals(wrapperImage, originalWrapperImage));
+ }
+}
+
static QShader loadShader(const char *name)
{
QFile f(QString::fromUtf8(name));