summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/gui/rhi/qrhi.cpp7
-rw-r--r--src/gui/rhi/qrhi_p.h3
-rw-r--r--src/gui/rhi/qrhid3d11.cpp4
-rw-r--r--src/gui/rhi/qrhigles2.cpp4
-rw-r--r--src/gui/rhi/qrhimetal.mm5
-rw-r--r--src/gui/rhi/qrhinull.cpp8
-rw-r--r--src/gui/rhi/qrhivulkan.cpp4
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp268
8 files changed, 294 insertions, 9 deletions
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 7d2af37e96..1eb26985bf 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -582,6 +582,13 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
practice this will be reported as unsupported with OpenGL ES 2.0 and OpenGL
2.x contexts, because GLSL 100 es and versions before 130 do not support
this function.
+
+ \value RenderToNonBaseMipLevel Indicates that specifying a mip level other
+ than 0 is supported when creating a QRhiTextureRenderTarget with a
+ QRhiTexture as its color attachment. When not supported, build() will fail
+ whenever the target mip level is not zero. In practice this feature will be
+ unsupported with OpenGL ES 2.0, while it will likely be supported everywhere
+ else.
*/
/*!
diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h
index 5c4cf4a144..5ef415e96c 100644
--- a/src/gui/rhi/qrhi_p.h
+++ b/src/gui/rhi/qrhi_p.h
@@ -1446,7 +1446,8 @@ public:
TriangleFanTopology,
ReadBackNonUniformBuffer,
ReadBackNonBaseMipLevel,
- TexelFetch
+ TexelFetch,
+ RenderToNonBaseMipLevel
};
enum BeginFrameFlag {
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index 0665304a19..c3c40b4cc4 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -470,6 +470,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::TexelFetch:
return true;
+ case QRhi::RenderToNonBaseMipLevel:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -3243,7 +3245,7 @@ bool QD3D11TextureRenderTarget::build()
}
ownsRtv[attIndex] = true;
if (attIndex == 0) {
- d.pixelSize = texD->pixelSize();
+ d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize());
d.sampleCount = int(texD->sampleDesc.Count);
}
} else if (rb) {
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 227dace90f..1e97d2ed0c 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -769,6 +769,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return caps.nonBaseLevelFramebufferTexture;
case QRhi::TexelFetch:
return caps.texelFetch;
+ case QRhi::RenderToNonBaseMipLevel:
+ return caps.nonBaseLevelFramebufferTexture;
default:
Q_UNREACHABLE();
return false;
@@ -3930,7 +3932,7 @@ bool QGles2TextureRenderTarget::build()
rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.layer()),
texD->texture, colorAtt.level());
if (attIndex == 0) {
- d.pixelSize = texD->pixelSize();
+ d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize());
d.sampleCount = 1;
}
} else if (renderBuffer) {
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index 0806c8a052..f021e78097 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -564,6 +564,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::TexelFetch:
return true;
+ case QRhi::RenderToNonBaseMipLevel:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -2863,6 +2865,7 @@ QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDesc
bool QMetalTextureRenderTarget::build()
{
+ QRHI_RES_RHI(QRhiMetal);
const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments();
Q_ASSERT(hasColorAttachments || m_desc.depthTexture());
Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
@@ -2879,7 +2882,7 @@ bool QMetalTextureRenderTarget::build()
if (texD) {
dst = texD->d->tex;
if (attIndex == 0) {
- d->pixelSize = texD->pixelSize();
+ d->pixelSize = rhiD->q->sizeForMipLevel(it->level(), texD->pixelSize());
d->sampleCount = texD->samples;
}
} else if (rbD) {
diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp
index 8c07e09b32..7b18c9156b 100644
--- a/src/gui/rhi/qrhinull.cpp
+++ b/src/gui/rhi/qrhinull.cpp
@@ -740,11 +740,13 @@ QRhiRenderPassDescriptor *QNullTextureRenderTarget::newCompatibleRenderPassDescr
bool QNullTextureRenderTarget::build()
{
+ QRHI_RES_RHI(QRhiNull);
d.rp = QRHI_RES(QNullRenderPassDescriptor, m_renderPassDesc);
if (m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments()) {
- QRhiTexture *tex = m_desc.cbeginColorAttachments()->texture();
- QRhiRenderBuffer *rb = m_desc.cbeginColorAttachments()->renderBuffer();
- d.pixelSize = tex ? tex->pixelSize() : rb->pixelSize();
+ const QRhiColorAttachment *colorAtt = m_desc.cbeginColorAttachments();
+ QRhiTexture *tex = colorAtt->texture();
+ QRhiRenderBuffer *rb = colorAtt->renderBuffer();
+ d.pixelSize = tex ? rhiD->q->sizeForMipLevel(colorAtt->level(), tex->pixelSize()) : rb->pixelSize();
} else if (m_desc.depthStencilBuffer()) {
d.pixelSize = m_desc.depthStencilBuffer()->pixelSize();
} else if (m_desc.depthTexture()) {
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index 211ee195e5..49e634e248 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -4023,6 +4023,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::TexelFetch:
return true;
+ case QRhi::RenderToNonBaseMipLevel:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -5911,7 +5913,7 @@ bool QVkTextureRenderTarget::build()
}
views.append(rtv[attIndex]);
if (attIndex == 0) {
- d.pixelSize = texD->pixelSize();
+ d.pixelSize = rhiD->q->sizeForMipLevel(it->level(), texD->pixelSize());
d.sampleCount = texD->samples;
}
} else if (rbD) {
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index c1793d7d4a..f437a7f1f8 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -89,6 +89,10 @@ private slots:
void invalidPipeline();
void renderToTextureSimple_data();
void renderToTextureSimple();
+ void renderToTextureMip_data();
+ void renderToTextureMip();
+ void renderToTextureCubemapFace_data();
+ void renderToTextureCubemapFace();
void renderToTextureTexturedQuad_data();
void renderToTextureTexturedQuad();
void renderToTextureArrayOfTexturedQuad_data();
@@ -299,7 +303,8 @@ void tst_QRhi::create()
QRhi::TriangleFanTopology,
QRhi::ReadBackNonUniformBuffer,
QRhi::ReadBackNonBaseMipLevel,
- QRhi::TexelFetch
+ QRhi::TexelFetch,
+ QRhi::RenderToNonBaseMipLevel
};
for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->isFeatureSupported(features[i]);
@@ -1341,6 +1346,267 @@ void tst_QRhi::renderToTextureSimple()
QVERIFY(redCount > blueCount);
}
+void tst_QRhi::renderToTextureMip_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureMip()
+{
+ 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 rendering");
+
+ if (!rhi->isFeatureSupported(QRhi::RenderToNonBaseMipLevel))
+ QSKIP("Rendering to non-base mip levels is not supported on this platform, skipping test");
+
+ const QSize baseLevelSize(1024, 1024);
+ const int LEVEL = 3; // render into mip #3 (128x128)
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, baseLevelSize, 1,
+ QRhiTexture::RenderTarget
+ | QRhiTexture::UsedAsTransferSource
+ | QRhiTexture::MipMapped));
+ QVERIFY(texture->build());
+
+ QRhiColorAttachment colorAtt(texture.data());
+ colorAtt.setLevel(LEVEL);
+ QRhiTextureRenderTargetDescription rtDesc(colorAtt);
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->build());
+
+ QCOMPARE(rt->pixelSize(), rhi->sizeForMipLevel(LEVEL, baseLevelSize));
+ const QSize mipSize(baseLevelSize.width() >> LEVEL, baseLevelSize.height() >> LEVEL);
+ QCOMPARE(rt->pixelSize(), mipSize);
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ static const float vertices[] = {
+ -1.0f, -1.0f,
+ 1.0f, -1.0f,
+ 0.0f, 1.0f
+ };
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
+ QVERIFY(vbuf->build());
+ updates->uploadStaticBuffer(vbuf.data(), vertices);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->build());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ QShader vs = loadShader(":/data/simple.vert.qsb");
+ QVERIFY(vs.isValid());
+ QShader fs = loadShader(":/data/simple.frag.qsb");
+ QVERIFY(fs.isValid());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 2 * sizeof(float) } });
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->build());
+
+ cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
+ QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbindings);
+ cb->draw(3);
+
+ QRhiReadbackResult readResult;
+ QImage result;
+ readResult.completed = [&readResult, &result] {
+ result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ QRhiReadbackDescription readbackDescription(texture.data());
+ readbackDescription.setLevel(LEVEL);
+ readbackBatch->readBackTexture(readbackDescription, &readResult);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ if (!rhi->isFeatureSupported(QRhi::ReadBackNonBaseMipLevel))
+ QSKIP("Reading back non-base mip levels is not supported on this platform, skipping readback");
+
+ QCOMPARE(result.size(), mipSize);
+
+ if (impl == QRhi::Null)
+ return;
+
+ const int y = 100;
+ const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
+ int x = result.width() - 1;
+ int redCount = 0;
+ int blueCount = 0;
+ const int maxFuzz = 1;
+ while (x-- >= 0) {
+ const QRgb c(*p++);
+ if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0)
+ ++redCount;
+ else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz))
+ ++blueCount;
+ else
+ QFAIL("Encountered a pixel that is neither red or blue");
+ }
+
+ QCOMPARE(redCount + blueCount, mipSize.width());
+
+ if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC())
+ QVERIFY(redCount > blueCount); // 100, 28
+ else
+ QVERIFY(redCount < blueCount); // 28, 100
+}
+
+void tst_QRhi::renderToTextureCubemapFace_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureCubemapFace()
+{
+ 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 rendering");
+
+ const QSize outputSize(512, 512); // width must be same as height
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1,
+ QRhiTexture::RenderTarget
+ | QRhiTexture::UsedAsTransferSource
+ | QRhiTexture::CubeMap)); // will be a cubemap, so 6 layers
+ QVERIFY(texture->build());
+
+ const int LAYER = 1; // render into the layer for face -X
+ const int BAD_LAYER = 2; // +Y
+
+ QRhiColorAttachment colorAtt(texture.data());
+ colorAtt.setLayer(LAYER);
+ QRhiTextureRenderTargetDescription rtDesc(colorAtt);
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->build());
+
+ QCOMPARE(rt->pixelSize(), texture->pixelSize());
+ QCOMPARE(rt->pixelSize(), outputSize);
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ static const float vertices[] = {
+ -1.0f, -1.0f,
+ 1.0f, -1.0f,
+ 0.0f, 1.0f
+ };
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
+ QVERIFY(vbuf->build());
+ updates->uploadStaticBuffer(vbuf.data(), vertices);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->build());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ QShader vs = loadShader(":/data/simple.vert.qsb");
+ QVERIFY(vs.isValid());
+ QShader fs = loadShader(":/data/simple.frag.qsb");
+ QVERIFY(fs.isValid());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 2 * sizeof(float) } });
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->build());
+
+ cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
+ QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbindings);
+ cb->draw(3);
+
+ QRhiReadbackResult readResult;
+ QImage result;
+ readResult.completed = [&readResult, &result] {
+ result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ QRhiReadbackDescription readbackDescription(texture.data());
+ readbackDescription.setLayer(LAYER);
+ readbackBatch->readBackTexture(readbackDescription, &readResult);
+
+ // also read back a layer we did not render into
+ QRhiReadbackResult readResult2;
+ QImage result2;
+ readResult2.completed = [&readResult2, &result2] {
+ result2 = QImage(reinterpret_cast<const uchar *>(readResult2.data.constData()),
+ readResult2.pixelSize.width(), readResult2.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied);
+ };
+ QRhiReadbackDescription readbackDescription2(texture.data());
+ readbackDescription2.setLayer(BAD_LAYER);
+ readbackBatch->readBackTexture(readbackDescription2, &readResult2);
+
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ QCOMPARE(result.size(), outputSize);
+ QCOMPARE(result2.size(), outputSize);
+
+ if (impl == QRhi::Null)
+ return;
+
+ // just want to ensure that we did not read the same thing back twice, i.e.
+ // that the 'layer' parameter was not ignored
+ QVERIFY(result != result2);
+
+ const int y = 100;
+ const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
+ int x = result.width() - 1;
+ int redCount = 0;
+ int blueCount = 0;
+ const int maxFuzz = 1;
+ while (x-- >= 0) {
+ const QRgb c(*p++);
+ if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0)
+ ++redCount;
+ else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz))
+ ++blueCount;
+ else
+ QFAIL("Encountered a pixel that is neither red or blue");
+ }
+
+ QCOMPARE(redCount + blueCount, outputSize.width());
+
+ if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC())
+ QVERIFY(redCount < blueCount); // 100, 412
+ else
+ QVERIFY(redCount > blueCount); // 412, 100
+}
+
void tst_QRhi::renderToTextureTexturedQuad_data()
{
rhiTestData();