summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2019-10-04 15:46:49 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2019-10-09 17:15:05 +0200
commit9c466946d0c5b4e319f1c953caeea63f0d453138 (patch)
treea1af9792abfb2837804f95befacea804f50d0f55
parente22399af826b582e330a2772460d3bc0d98eb558 (diff)
rhi: Autotest basic texture operations
...and make the Null backend able to deal with these, for RGBA8 textures at least. Naturally it is all QImage and QPainter under the hood. Also fix a bug in the OpenGL backend, as discovered by the autotest: the size from the readback did not reflect the mip level. Task-number: QTBUG-78971 Change-Id: Ie424b268bf5feb09021099b67068f4418a9b583e Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
-rw-r--r--src/gui/rhi/qrhigles2.cpp5
-rw-r--r--src/gui/rhi/qrhinull.cpp116
-rw-r--r--src/gui/rhi/qrhinull_p_p.h5
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp408
4 files changed, 509 insertions, 25 deletions
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 13dc016fd6..a6094183fb 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -1532,8 +1532,9 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
trackedImageBarrier(cbD, texD, QGles2Texture::AccessRead);
cmd.args.readPixels.texture = texD ? texD->texture : 0;
if (texD) {
- cmd.args.readPixels.w = texD->m_pixelSize.width();
- cmd.args.readPixels.h = texD->m_pixelSize.height();
+ const QSize readImageSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
+ cmd.args.readPixels.w = readImageSize.width();
+ cmd.args.readPixels.h = readImageSize.height();
cmd.args.readPixels.format = texD->m_format;
const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap)
? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp
index de6616b677..d78b5e20c5 100644
--- a/src/gui/rhi/qrhinull.cpp
+++ b/src/gui/rhi/qrhinull.cpp
@@ -36,6 +36,7 @@
#include "qrhinull_p_p.h"
#include <qmath.h>
+#include <QPainter>
QT_BEGIN_NAMESPACE
@@ -385,6 +386,67 @@ QRhi::FrameOpResult QRhiNull::finish()
return QRhi::FrameOpSuccess;
}
+void QRhiNull::simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u)
+{
+ QNullTexture *texD = QRHI_RES(QNullTexture, u.dst);
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) {
+ if (!subresDesc.image().isNull()) {
+ const QImage src = subresDesc.image();
+ QPainter painter(&texD->image[layer][level]);
+ const QSize srcSize = subresDesc.sourceSize().isEmpty()
+ ? src.size() : subresDesc.sourceSize();
+ painter.drawImage(subresDesc.destinationTopLeft(), src,
+ QRect(subresDesc.sourceTopLeft(), srcSize));
+ } else if (!subresDesc.data().isEmpty()) {
+ const QSize subresSize = q->sizeForMipLevel(level, texD->pixelSize());
+ int w = subresSize.width();
+ int h = subresSize.height();
+ if (!subresDesc.sourceSize().isEmpty()) {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+ // sourceTopLeft is not supported on this path as per QRhi docs
+ const char *src = subresDesc.data().constData();
+ const int srcBpl = w * 4;
+ 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,
+ size_t(srcBpl));
+ }
+ }
+ }
+ }
+ }
+}
+
+void QRhiNull::simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u)
+{
+ QNullTexture *srcD = QRHI_RES(QNullTexture, u.src);
+ QNullTexture *dstD = QRHI_RES(QNullTexture, u.dst);
+ const QImage &srcImage(srcD->image[u.desc.sourceLayer()][u.desc.sourceLevel()]);
+ QImage &dstImage(dstD->image[u.desc.destinationLayer()][u.desc.destinationLevel()]);
+ const QPoint dstPos = u.desc.destinationTopLeft();
+ const QSize size = u.desc.pixelSize().isEmpty() ? srcD->pixelSize() : u.desc.pixelSize();
+ const QPoint srcPos = u.desc.sourceTopLeft();
+
+ QPainter painter(&dstImage);
+ painter.drawImage(QRect(dstPos, size), srcImage, QRect(srcPos, size));
+}
+
+void QRhiNull::simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u)
+{
+ QNullTexture *texD = QRHI_RES(QNullTexture, u.dst);
+ const QSize baseSize = texD->pixelSize();
+ const int levelCount = q->mipLevelsForSize(baseSize);
+ for (int level = 1; level < levelCount; ++level)
+ texD->image[0][level] = texD->image[0][0].scaled(q->sizeForMipLevel(level, baseSize));
+}
+
void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
{
Q_UNUSED(cb);
@@ -405,22 +467,42 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re
}
}
for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
- if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ if (u.dst->format() == QRhiTexture::RGBA8)
+ simulateTextureUpload(u);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ if (u.src->format() == QRhiTexture::RGBA8 && u.dst->format() == QRhiTexture::RGBA8)
+ simulateTextureCopy(u);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
QRhiReadbackResult *result = u.result;
- QRhiTexture *tex = u.rb.texture();
- if (tex) {
- result->format = tex->format();
- result->pixelSize = q->sizeForMipLevel(u.rb.level(), tex->pixelSize());
+ QNullTexture *texD = QRHI_RES(QNullTexture, u.rb.texture());
+ if (texD) {
+ result->format = texD->format();
+ result->pixelSize = q->sizeForMipLevel(u.rb.level(), texD->pixelSize());
} else {
Q_ASSERT(currentSwapChain);
result->format = QRhiTexture::RGBA8;
result->pixelSize = currentSwapChain->currentPixelSize();
}
+ quint32 bytesPerLine = 0;
quint32 byteSize = 0;
- textureFormatInfo(result->format, result->pixelSize, nullptr, &byteSize);
- result->data.fill(0, int(byteSize));
+ textureFormatInfo(result->format, result->pixelSize, &bytesPerLine, &byteSize);
+ if (result->format == QRhiTexture::RGBA8) {
+ result->data.resize(int(byteSize));
+ const QImage &src(texD->image[u.rb.layer()][u.rb.level()]);
+ char *dst = result->data.data();
+ for (int y = 0, h = src.height(); y < h; ++y) {
+ memcpy(dst, src.constScanLine(y), bytesPerLine);
+ dst += bytesPerLine;
+ }
+ } else {
+ result->data.fill(0, int(byteSize));
+ }
if (result->completed)
result->completed();
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
+ if (u.dst->format() == QRhiTexture::RGBA8)
+ simulateTextureGenMips(u);
}
}
ud->free();
@@ -532,22 +614,36 @@ void QNullTexture::release()
bool QNullTexture::build()
{
+ QRHI_RES_RHI(QRhiNull);
const bool isCube = m_flags.testFlag(CubeMap);
const bool hasMipMaps = m_flags.testFlag(MipMapped);
QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
- const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1;
+ const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ const int layerCount = isCube ? 6 : 1;
+
+ if (m_format == RGBA8) {
+ for (int layer = 0; layer < layerCount; ++layer) {
+ for (int level = 0; level < mipLevelCount; ++level) {
+ image[layer][level] = QImage(rhiD->q->sizeForMipLevel(level, size),
+ QImage::Format_RGBA8888_Premultiplied);
+ image[layer][level].fill(Qt::yellow);
+ }
+ }
+ }
+
QRHI_PROF;
- QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1));
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, layerCount, 1));
return true;
}
bool QNullTexture::buildFrom(const QRhiNativeHandles *src)
{
Q_UNUSED(src);
+ QRHI_RES_RHI(QRhiNull);
const bool isCube = m_flags.testFlag(CubeMap);
const bool hasMipMaps = m_flags.testFlag(MipMapped);
QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
- const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1;
+ const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
QRHI_PROF;
QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : 1, 1));
return true;
diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h
index 93e298481c..ce517bfa63 100644
--- a/src/gui/rhi/qrhinull_p_p.h
+++ b/src/gui/rhi/qrhinull_p_p.h
@@ -84,6 +84,7 @@ struct QNullTexture : public QRhiTexture
const QRhiNativeHandles *nativeHandles() override;
QRhiNullTextureNativeHandles nativeHandlesStruct;
+ QImage image[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS];
};
struct QNullSampler : public QRhiSampler
@@ -288,6 +289,10 @@ public:
void releaseCachedResources() override;
bool isDeviceLost() const override;
+ void simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u);
+ void simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u);
+ void simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u);
+
QRhiNullNativeHandles nativeHandlesStruct;
QRhiSwapChain *currentSwapChain = nullptr;
QNullCommandBuffer offscreenCommandBuffer;
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index 409152db0d..a17495f317 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -30,6 +30,8 @@
#include <QThread>
#include <QFile>
#include <QOffscreenSurface>
+#include <QPainter>
+
#include <QtGui/private/qrhi_p.h>
#include <QtGui/private/qrhinull_p.h>
@@ -73,6 +75,12 @@ private slots:
void nativeHandles();
void resourceUpdateBatchBuffer_data();
void resourceUpdateBatchBuffer();
+ void resourceUpdateBatchRGBATextureUpload_data();
+ void resourceUpdateBatchRGBATextureUpload();
+ void resourceUpdateBatchRGBATextureCopy_data();
+ void resourceUpdateBatchRGBATextureCopy();
+ void resourceUpdateBatchRGBATextureMip_data();
+ void resourceUpdateBatchRGBATextureMip();
private:
struct {
@@ -505,6 +513,23 @@ void tst_QRhi::nativeHandles()
}
}
+static bool submitResourceUpdates(QRhi *rhi, QRhiResourceUpdateBatch *batch)
+{
+ QRhiCommandBuffer *cb = nullptr;
+ QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb);
+ if (result != QRhi::FrameOpSuccess) {
+ qWarning("beginOffscreenFrame returned %d", result);
+ return false;
+ }
+ if (!cb) {
+ qWarning("No command buffer from beginOffscreenFrame");
+ return false;
+ }
+ cb->resourceUpdate(batch);
+ rhi->endOffscreenFrame();
+ return true;
+}
+
void tst_QRhi::resourceUpdateBatchBuffer_data()
{
rhiTestData();
@@ -517,7 +542,7 @@ void tst_QRhi::resourceUpdateBatchBuffer()
QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
if (!rhi)
- QSKIP("QRhi could not be created, skipping testing resource updates");
+ QSKIP("QRhi could not be created, skipping testing buffer resource updates");
const int bufferSize = 23;
const QByteArray a(bufferSize, 'A');
@@ -539,12 +564,7 @@ void tst_QRhi::resourceUpdateBatchBuffer()
readResult.completed = [&readCompleted] { readCompleted = true; };
batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult);
- QRhiCommandBuffer *cb = nullptr;
- QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb);
- QVERIFY(result == QRhi::FrameOpSuccess);
- QVERIFY(cb);
- cb->resourceUpdate(batch);
- rhi->endOffscreenFrame();
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
// Offscreen frames are synchronous, so the readback must have
// completed at this point. With swapchain frames this would not be the
@@ -573,12 +593,7 @@ void tst_QRhi::resourceUpdateBatchBuffer()
if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer))
batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult);
- QRhiCommandBuffer *cb = nullptr;
- QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb);
- QVERIFY(result == QRhi::FrameOpSuccess);
- QVERIFY(cb);
- cb->resourceUpdate(batch);
- rhi->endOffscreenFrame();
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) {
QVERIFY(readCompleted);
@@ -591,5 +606,372 @@ void tst_QRhi::resourceUpdateBatchBuffer()
}
}
+inline bool imageRGBAEquals(const QImage &a, const QImage &b)
+{
+ const int maxFuzz = 1;
+
+ if (a.size() != b.size())
+ return false;
+
+ const QImage image0 = a.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
+ const QImage image1 = b.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
+
+ const int width = image0.width();
+ const int height = image0.height();
+ for (int y = 0; y < height; ++y) {
+ const quint32 *p0 = reinterpret_cast<const quint32 *>(image0.constScanLine(y));
+ const quint32 *p1 = reinterpret_cast<const quint32 *>(image1.constScanLine(y));
+ int x = width - 1;
+ while (x-- >= 0) {
+ const QRgb c0(*p0++);
+ const QRgb c1(*p1++);
+ const int red = qAbs(qRed(c0) - qRed(c1));
+ const int green = qAbs(qGreen(c0) - qGreen(c1));
+ const int blue = qAbs(qBlue(c0) - qBlue(c1));
+ const int alpha = qAbs(qAlpha(c0) - qAlpha(c1));
+ if (red > maxFuzz || green > maxFuzz || blue > maxFuzz || alpha > maxFuzz)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void tst_QRhi::resourceUpdateBatchRGBATextureUpload_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::resourceUpdateBatchRGBATextureUpload()
+{
+ 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");
+
+ QImage image(234, 123, QImage::Format_RGBA8888_Premultiplied);
+ image.fill(Qt::red);
+ QPainter painter;
+ const QPoint greenRectPos(35, 50);
+ const QSize greenRectSize(100, 50);
+ painter.begin(&image);
+ painter.fillRect(QRect(greenRectPos, greenRectSize), Qt::green);
+ painter.end();
+
+ // simple image upload; uploading and reading back RGBA8 is supported by the Null backend even
+ {
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, image.size(),
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ batch->uploadTexture(texture.data(), image);
+
+ QRhiReadbackResult readResult;
+ bool readCompleted = false;
+ readResult.completed = [&readCompleted] { readCompleted = true; };
+ batch->readBackTexture(texture.data(), &readResult);
+
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ // like with buffers, the readback is now complete due to endOffscreenFrame()
+ QVERIFY(readCompleted);
+ QCOMPARE(readResult.format, QRhiTexture::RGBA8);
+ QCOMPARE(readResult.pixelSize, image.size());
+
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ image.format());
+
+ QVERIFY(imageRGBAEquals(image, wrapperImage));
+ }
+
+ // the same with raw data
+ {
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, image.size(),
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+
+ QRhiTextureUploadEntry upload(0, 0, { image.constBits(), int(image.sizeInBytes()) });
+ 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, image.size());
+
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ image.format());
+
+ QVERIFY(imageRGBAEquals(image, wrapperImage));
+ }
+
+ // partial image upload at a non-zero destination position
+ {
+ const QSize copySize(30, 40);
+ const int gap = 10;
+ const QSize fullSize(copySize.width() + gap, copySize.height() + gap);
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, fullSize,
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+
+ QImage clearImage(fullSize, image.format());
+ clearImage.fill(Qt::black);
+ batch->uploadTexture(texture.data(), clearImage);
+
+ // copy green pixels of copySize to (gap, gap), leaving a black bar of
+ // gap pixels on the left and top
+ QRhiTextureSubresourceUploadDescription desc;
+ desc.setImage(image);
+ desc.setSourceSize(copySize);
+ desc.setDestinationTopLeft(QPoint(gap, gap));
+ desc.setSourceTopLeft(greenRectPos);
+
+ batch->uploadTexture(texture.data(), QRhiTextureUploadDescription({ 0, 0, desc }));
+
+ 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, clearImage.size());
+
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ image.format());
+
+ QVERIFY(!imageRGBAEquals(clearImage, wrapperImage));
+
+ QImage expectedImage = clearImage;
+ QPainter painter(&expectedImage);
+ painter.fillRect(QRect(QPoint(gap, gap), QSize(copySize)), Qt::green);
+ painter.end();
+
+ QVERIFY(imageRGBAEquals(expectedImage, wrapperImage));
+ }
+
+ // the same (partial upload) with raw data as source
+ {
+ const QSize copySize(30, 40);
+ const int gap = 10;
+ const QSize fullSize(copySize.width() + gap, copySize.height() + gap);
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, fullSize,
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+
+ QImage clearImage(fullSize, image.format());
+ clearImage.fill(Qt::black);
+ batch->uploadTexture(texture.data(), clearImage);
+
+ // SourceTopLeft is not supported for non-QImage-based uploads.
+ const QImage im = image.copy(QRect(greenRectPos, copySize));
+ QRhiTextureSubresourceUploadDescription desc;
+ desc.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(im.constBits()),
+ int(im.sizeInBytes())));
+ desc.setSourceSize(copySize);
+ desc.setDestinationTopLeft(QPoint(gap, gap));
+
+ batch->uploadTexture(texture.data(), QRhiTextureUploadDescription({ 0, 0, desc }));
+
+ 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, clearImage.size());
+
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ image.format());
+
+ QVERIFY(!imageRGBAEquals(clearImage, wrapperImage));
+
+ QImage expectedImage = clearImage;
+ QPainter painter(&expectedImage);
+ painter.fillRect(QRect(QPoint(gap, gap), QSize(copySize)), Qt::green);
+ painter.end();
+
+ QVERIFY(imageRGBAEquals(expectedImage, wrapperImage));
+ }
+}
+
+void tst_QRhi::resourceUpdateBatchRGBATextureCopy_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::resourceUpdateBatchRGBATextureCopy()
+{
+ 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");
+
+ QImage red(256, 256, QImage::Format_RGBA8888_Premultiplied);
+ red.fill(Qt::red);
+
+ QImage green(35, 73, red.format());
+ green.fill(Qt::green);
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+
+ QScopedPointer<QRhiTexture> redTexture(rhi->newTexture(QRhiTexture::RGBA8, red.size(),
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(redTexture->build());
+ batch->uploadTexture(redTexture.data(), red);
+
+ QScopedPointer<QRhiTexture> greenTexture(rhi->newTexture(QRhiTexture::RGBA8, green.size(),
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(greenTexture->build());
+ batch->uploadTexture(greenTexture.data(), green);
+
+ // 1. simple copy red -> texture; 2. subimage copy green -> texture; 3. partial subimage copy green -> texture
+ {
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, red.size(),
+ 1, QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ // 1.
+ batch->copyTexture(texture.data(), redTexture.data());
+
+ QRhiReadbackResult readResult;
+ bool readCompleted = false;
+ readResult.completed = [&readCompleted] { readCompleted = true; };
+ batch->readBackTexture(texture.data(), &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(readCompleted);
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ red.format());
+ QVERIFY(imageRGBAEquals(red, wrapperImage));
+
+ batch = rhi->nextResourceUpdateBatch();
+ readCompleted = false;
+
+ // 2.
+ QRhiTextureCopyDescription copyDesc;
+ copyDesc.setDestinationTopLeft(QPoint(15, 23));
+ batch->copyTexture(texture.data(), greenTexture.data(), copyDesc);
+
+ batch->readBackTexture(texture.data(), &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(readCompleted);
+ wrapperImage = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ red.format());
+
+ QImage expectedImage = red;
+ QPainter painter(&expectedImage);
+ painter.drawImage(copyDesc.destinationTopLeft(), green);
+ painter.end();
+
+ QVERIFY(imageRGBAEquals(expectedImage, wrapperImage));
+
+ batch = rhi->nextResourceUpdateBatch();
+ readCompleted = false;
+
+ // 3.
+ copyDesc.setDestinationTopLeft(QPoint(125, 89));
+ copyDesc.setSourceTopLeft(QPoint(5, 5));
+ copyDesc.setPixelSize(QSize(26, 45));
+ batch->copyTexture(texture.data(), greenTexture.data(), copyDesc);
+
+ batch->readBackTexture(texture.data(), &readResult);
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(readCompleted);
+ wrapperImage = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ red.format());
+
+ painter.begin(&expectedImage);
+ painter.drawImage(copyDesc.destinationTopLeft(), green,
+ QRect(copyDesc.sourceTopLeft(), copyDesc.pixelSize()));
+ painter.end();
+
+ QVERIFY(imageRGBAEquals(expectedImage, wrapperImage));
+ }
+}
+
+void tst_QRhi::resourceUpdateBatchRGBATextureMip_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::resourceUpdateBatchRGBATextureMip()
+{
+ 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");
+
+
+ QImage red(512, 512, QImage::Format_RGBA8888_Premultiplied);
+ red.fill(Qt::red);
+
+ const QRhiTexture::Flags textureFlags =
+ QRhiTexture::UsedAsTransferSource
+ | QRhiTexture::MipMapped
+ | QRhiTexture::UsedWithGenerateMips;
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), 1, textureFlags));
+ QVERIFY(texture->build());
+
+ QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
+ batch->uploadTexture(texture.data(), red);
+ batch->generateMips(texture.data());
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+
+ const int levelCount = rhi->mipLevelsForSize(red.size());
+ QCOMPARE(levelCount, 10);
+ for (int level = 0; level < levelCount; ++level) {
+ batch = rhi->nextResourceUpdateBatch();
+
+ QRhiReadbackDescription readDesc(texture.data());
+ readDesc.setLevel(level);
+ QRhiReadbackResult readResult;
+ bool readCompleted = false;
+ readResult.completed = [&readCompleted] { readCompleted = true; };
+ batch->readBackTexture(readDesc, &readResult);
+
+ QVERIFY(submitResourceUpdates(rhi.data(), batch));
+ QVERIFY(readCompleted);
+
+ const QSize expectedSize = rhi->sizeForMipLevel(level, texture->pixelSize());
+ QCOMPARE(readResult.pixelSize, expectedSize);
+
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ red.format());
+
+ // Compare to a scaled version; we can do this safely only because we
+ // only have plain red pixels in the source image.
+ QImage expectedImage = red.scaled(expectedSize);
+ QVERIFY(imageRGBAEquals(expectedImage, wrapperImage));
+ }
+}
+
#include <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)