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>
This commit is contained in:
parent
5ce367a552
commit
7ccd2d0246
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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)),
|
||||
|
@ -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()]);
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user