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 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 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 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 rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer 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 vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QScopedPointer 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(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(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 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 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 rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer 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 vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QScopedPointer 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(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(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(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();