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 <andy.nichols@qt.io>
bb10
Laszlo Agocs 2021-03-25 12:39:02 +01:00
parent 2d9cc639a4
commit 8827cd657d
8 changed files with 233 additions and 3 deletions

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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;
};

View File

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

View File

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

View File

@ -37,6 +37,7 @@
#if QT_CONFIG(opengl)
# include <QOpenGLContext>
# include <QOpenGLFunctions>
# include <QtGui/private/qrhigles2_p.h>
# 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<QRhi::Implementation>("impl");
QTest::addColumn<QRhiInitParams *>("initParams");
#ifdef TST_GL
QTest::newRow("OpenGL") << QRhi::OpenGLES2 << static_cast<QRhiInitParams *>(&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 <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->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<QRhi> 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<QRhiTexture> 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<const uchar *>(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<QRhi> 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<QRhiRenderBuffer> rb(rhi->newRenderBuffer(QRhiRenderBuffer::Color, size));
QVERIFY(rb->createFrom({ b }));
QScopedPointer<QRhiRenderBuffer> depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size));
QVERIFY(depthStencil->create());
QRhiColorAttachment att(rb.data());
QRhiTextureRenderTargetDescription rtDesc(att);
rtDesc.setDepthStencilBuffer(depthStencil.data());
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
QScopedPointer<QRhiRenderPassDescriptor> 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<const uchar *>(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 <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)