summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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)