rhi: Take mip size into account for render target size

Also extend autotesting, both for rendering into a given mip level
and for rendering into a given cubemap face.

Change-Id: Ida94b71150477ceb50a3b5616d8b7be13174558b
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2020-04-20 13:00:39 +02:00
parent 4545eadd3e
commit 3ef7a760ff
8 changed files with 294 additions and 9 deletions

View File

@ -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.
*/
/*!

View File

@ -1446,7 +1446,8 @@ public:
TriangleFanTopology,
ReadBackNonUniformBuffer,
ReadBackNonBaseMipLevel,
TexelFetch
TexelFetch,
RenderToNonBaseMipLevel
};
enum BeginFrameFlag {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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()) {

View File

@ -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) {

View File

@ -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();