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
parent
2d9cc639a4
commit
8827cd657d
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue