From 8827cd657d12a037deb3e6f4f76271ee0bbd0b13 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Thu, 25 Mar 2021 12:39:02 +0100 Subject: [PATCH] rhi: gl: Add support for importing an existing renderbuffer object Normally we only allow creating wrappers for texture objects. These can then be used with a QRhiTextureRenderTarget to allow rendering into an externally created texture. With OpenGL (ES), there are additional, special cases, especially on embedded. Consider EGLImages for example. An EGLImageKHR can be bound to a renderbuffer object (glEGLImageTargetRenderbufferStorageOES), which can then be associated with a framebuffer object to allow rendering into the external buffer represented by the EGLImage. To implement the same via QRhi one needs a way to create a wrapping QRhiRenderBuffer for the native OpenGL renderbuffer object. Here we add a createFrom() to QRhiRenderBuffer, while providing a dummy, default implementation. The only real implementation is in the OpenGL backend, which simply takes a renderbuffer id, without taking ownership. Task-number: QTBUG-92116 Change-Id: I4e68e665fb35a7d7803b7780db901c8bed5740e2 Reviewed-by: Andy Nichols --- src/gui/rhi/qrhi.cpp | 47 +++++++++ src/gui/rhi/qrhi_p.h | 8 +- src/gui/rhi/qrhid3d11.cpp | 2 + src/gui/rhi/qrhigles2.cpp | 33 ++++++- src/gui/rhi/qrhigles2_p_p.h | 2 + src/gui/rhi/qrhimetal.mm | 2 + src/gui/rhi/qrhivulkan.cpp | 2 + tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 140 ++++++++++++++++++++++++++- 8 files changed, 233 insertions(+), 3 deletions(-) diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 8c24f11079..242f244c1d 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -642,6 +642,15 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") supported (which can happen when the underlying API is OpenGL ES 2.0 without support for GL_UNPACK_ROW_LENGTH), QRhiTextureSubresourceUploadDescription::setDataStride() must not be used. + + \value RenderBufferImport Indicates that QRhiRenderBuffer::createFrom() is + supported. For most graphics APIs this is not sensible because + QRhiRenderBuffer encapsulates texture objects internally, just like + QRhiTexture. With OpenGL however, renderbuffer object exist as a separate + object type in the API, and in certain environments (for example, where one + may want to associated a renderbuffer object with an EGLImage object) it is + important to allow wrapping an existing OpenGL renderbuffer object with a + QRhiRenderBuffer. */ /*! @@ -2305,6 +2314,44 @@ QRhiResource::Type QRhiRenderBuffer::resourceType() const Regardless of the return value, calling destroy() is always safe. */ +/*! + Similar to create() except that no new native renderbuffer objects are + created. Instead, the native renderbuffer object specified by \a src is + used. + + This allows importing an existing renderbuffer object (which must belong to + the same device or sharing context, depending on the graphics API) from an + external graphics engine. + + \note This is currently applicable to OpenGL only. This function exists + solely to allow importing a renderbuffer object that is bound to some + special, external object, such as an EGLImageKHR. Once the application + performed the glEGLImageTargetRenderbufferStorageOES call, the renderbuffer + object can be passed to this function to create a wrapping + QRhiRenderBuffer, which in turn can be passed in as a color attachment to + a QRhiTextureRenderTarget to enable rendering to the EGLImage. + + \note pixelSize(), sampleCount(), and flags() must still be set correctly. + Passing incorrect sizes and other values to QRhi::newRenderBuffer() and + then following it with a createFrom() expecting that the native + renderbuffer object alone is sufficient to deduce such values is \b wrong + and will lead to problems. + + \note QRhiRenderBuffer does not take ownership of the native object, and + destroy() will not release that object. + + \note This function is only implemented when the QRhi::RenderBufferImport + feature is reported as \l{QRhi::isFeatureSupported()}{supported}. Otherwise, + the function does nothing and the return value is \c false. + + \return \c true when successful, \c false when not supported. + */ +bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src) +{ + Q_UNUSED(src); + return false; +} + /*! \fn QRhiTexture::Format QRhiRenderBuffer::backingFormat() const diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index b43d490ea9..c915f8df0d 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -918,6 +918,10 @@ public: }; Q_DECLARE_FLAGS(Flags, Flag) + struct NativeRenderBuffer { + quint64 object; + }; + QRhiResource::Type resourceType() const override; Type type() const { return m_type; } @@ -933,6 +937,7 @@ public: void setFlags(Flags h) { m_flags = h; } virtual bool create() = 0; + virtual bool createFrom(NativeRenderBuffer src); virtual QRhiTexture::Format backingFormat() const = 0; @@ -1534,7 +1539,8 @@ public: ScreenSpaceDerivatives, ReadBackAnyTextureFormat, PipelineCacheDataLoadSave, - ImageDataStride + ImageDataStride, + RenderBufferImport }; enum BeginFrameFlag { diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 46851ad612..8d15f2622f 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -544,6 +544,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const return false; case QRhi::ImageDataStride: return true; + case QRhi::RenderBufferImport: + return false; default: Q_UNREACHABLE(); return false; diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 43569d3752..8a5361b29a 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -1011,6 +1011,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const return caps.programBinary; case QRhi::ImageDataStride: return !caps.gles || caps.ctxMajor >= 3; + case QRhi::RenderBufferImport: + return true; default: Q_UNREACHABLE(); return false; @@ -4287,7 +4289,8 @@ void QGles2RenderBuffer::destroy() stencilRenderbuffer = 0; QRHI_RES_RHI(QRhiGles2); - rhiD->releaseQueue.append(e); + if (owns) + rhiD->releaseQueue.append(e); QRHI_PROF; QRHI_PROF_F(releaseRenderBuffer(this)); rhiD->unregisterResource(this); @@ -4375,6 +4378,34 @@ bool QGles2RenderBuffer::create() break; } + owns = true; + rhiD->registerResource(this); + return true; +} + +bool QGles2RenderBuffer::createFrom(NativeRenderBuffer src) +{ + if (!src.object) + return false; + + if (renderbuffer) + destroy(); + + QRHI_RES_RHI(QRhiGles2); + samples = rhiD->effectiveSampleCount(m_sampleCount); + + if (m_flags.testFlag(UsedWithSwapChainOnly)) + qWarning("RenderBuffer: UsedWithSwapChainOnly is meaningless when importing an existing native object"); + + if (!rhiD->ensureContext()) + return false; + + renderbuffer = src.object; + + QRHI_PROF; + QRHI_PROF_F(newRenderBuffer(this, false, false, samples)); + + owns = false; rhiD->registerResource(this); return true; } diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index 3d9fba80dc..cb6cd241ff 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -100,11 +100,13 @@ struct QGles2RenderBuffer : public QRhiRenderBuffer ~QGles2RenderBuffer(); void destroy() override; bool create() override; + bool createFrom(NativeRenderBuffer src) override; QRhiTexture::Format backingFormat() const override; GLuint renderbuffer = 0; GLuint stencilRenderbuffer = 0; // when packed depth-stencil not supported int samples; + bool owns = true; friend class QRhiGles2; }; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 850b3a91aa..384e23598c 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -595,6 +595,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const return false; case QRhi::ImageDataStride: return true; + case QRhi::RenderBufferImport: + return false; default: Q_UNREACHABLE(); return false; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index e7e31b1b2d..73288cb4a1 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -4243,6 +4243,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::ImageDataStride: return true; + case QRhi::RenderBufferImport: + return false; default: Q_UNREACHABLE(); return false; diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 076106a589..18eebaae1e 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -37,6 +37,7 @@ #if QT_CONFIG(opengl) # include +# include # include # define TST_GL #endif @@ -70,6 +71,7 @@ private slots: void cleanupTestCase(); void rhiTestData(); + void rhiTestDataOpenGL(); void create_data(); void create(); void nativeHandles_data(); @@ -129,6 +131,10 @@ private slots: void pipelineCache_data(); void pipelineCache(); + void textureImportOpenGL_data(); + void textureImportOpenGL(); + void renderbufferImportOpenGL_data(); + void renderbufferImportOpenGL(); private: void setWindowType(QWindow *window, QRhi::Implementation impl); @@ -214,6 +220,16 @@ void tst_QRhi::rhiTestData() #endif } +void tst_QRhi::rhiTestDataOpenGL() +{ + QTest::addColumn("impl"); + QTest::addColumn("initParams"); + +#ifdef TST_GL + QTest::newRow("OpenGL") << QRhi::OpenGLES2 << static_cast(&initParams.gl); +#endif +} + void tst_QRhi::create_data() { rhiTestData(); @@ -351,7 +367,8 @@ void tst_QRhi::create() QRhi::ScreenSpaceDerivatives, QRhi::ReadBackAnyTextureFormat, QRhi::PipelineCacheDataLoadSave, - QRhi::ImageDataStride + QRhi::ImageDataStride, + QRhi::RenderBufferImport }; for (size_t i = 0; i isFeatureSupported(features[i]); @@ -3689,5 +3706,126 @@ void tst_QRhi::pipelineCache() } } +void tst_QRhi::textureImportOpenGL_data() +{ + rhiTestDataOpenGL(); +} + +void tst_QRhi::textureImportOpenGL() +{ + QFETCH(QRhi::Implementation, impl); + if (impl != QRhi::OpenGLES2) + QSKIP("Skipping OpenGL-dependent test"); + +#ifdef TST_GL + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing native texture"); + + QVERIFY(rhi->makeThreadLocalNativeContextCurrent()); + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + QVERIFY(ctx); + QOpenGLFunctions *f = ctx->functions(); + + QImage image(320, 200, QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::red); + + GLuint t = 0; + f->glGenTextures(1, &t); + f->glBindTexture(GL_TEXTURE_2D, t); + f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); + + QScopedPointer tex(rhi->newTexture(QRhiTexture::RGBA8, image.size())); + QRhiTexture::NativeTexture nativeTex = { t, 0 }; + QVERIFY(tex->createFrom(nativeTex)); + QCOMPARE(tex->nativeTexture().object, nativeTex.object); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->readBackTexture(tex.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, image.size()); + QImage wrapperImage(reinterpret_cast(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + QVERIFY(imageRGBAEquals(image, wrapperImage)); + + f->glDeleteTextures(1, &t); +#endif +} + +void tst_QRhi::renderbufferImportOpenGL_data() +{ + rhiTestDataOpenGL(); +} + +void tst_QRhi::renderbufferImportOpenGL() +{ + QFETCH(QRhi::Implementation, impl); + if (impl != QRhi::OpenGLES2) + QSKIP("Skipping OpenGL-dependent test"); + +#ifdef TST_GL + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing native texture"); + + QVERIFY(rhi->makeThreadLocalNativeContextCurrent()); + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + QVERIFY(ctx); + QOpenGLFunctions *f = ctx->functions(); + + const QSize size(320, 200); + GLuint b = 0; + f->glGenRenderbuffers(1, &b); + f->glBindRenderbuffer(GL_RENDERBUFFER, b); + // in a real world use case this would be some extension, e.g. glEGLImageTargetRenderbufferStorageOES instead + f->glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, size.width(), size.height()); + f->glBindRenderbuffer(GL_RENDERBUFFER, 0); + + QScopedPointer rb(rhi->newRenderBuffer(QRhiRenderBuffer::Color, size)); + QVERIFY(rb->createFrom({ b })); + + QScopedPointer depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size)); + QVERIFY(depthStencil->create()); + QRhiColorAttachment att(rb.data()); + QRhiTextureRenderTargetDescription rtDesc(att); + rtDesc.setDepthStencilBuffer(depthStencil.data()); + QScopedPointer rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.data()); + QVERIFY(rt->create()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 }, nullptr, QRhiCommandBuffer::ExternalContent); + cb->beginExternal(); + QByteArray tmpBuf; + tmpBuf.resize(size.width() * size.height() * 4); + f->glReadPixels(0, 0, size.width(), size.height(), GL_RGBA, GL_UNSIGNED_BYTE, tmpBuf.data()); + cb->endExternal(); + cb->endPass(); + rhi->endOffscreenFrame(); + + f->glDeleteRenderbuffers(1, &b); + + QImage wrapperImage(reinterpret_cast(tmpBuf.constData()), + size.width(), size.height(), QImage::Format_RGBA8888_Premultiplied); + + QImage image(320, 200, QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::red); + QVERIFY(imageRGBAEquals(image, wrapperImage)); +#endif +} + #include QTEST_MAIN(tst_QRhi)