I'd like to implement an actual widget for Qt3D since QWidget::createWindowContainer
just doesn't cut it for me.
My first approach of letting a new class subclass QWidget
and QSurface
was not successful since the Qt3D code either expects a QWindow
or a QOffscreenSurface
in multiple places and I don't want to recompile the whole Qt3D base.
My second idea was to render the Qt3D content to an offscreen surface and then draw the texture on a quad in a QOpenGLWidget
. When I use a QRenderCapture
framegraph node to save the image rendered to the offscreen texture and then load the image into a QOpenGLTexture
and draw it in the QOpenGLWidget
's paintGL
function I can see the rendered image - i.e. rendering in Qt3D and also in the OpenGL widget works properly. This is just extremely slow compared to rendering the content from Qt3D directly.
Now, when I use the GLuint
returned by the QTexutre2D
to bind the texture during rendering of the QOpenGLWidget
, everything stays black.
Of course this would make sense, if the contexts of the QOpenGLWidget
and QT3D were completely separate. But by retrieving the AbstractRenderer
from the QRenderAspectPrivate
I was able to obtain the context that Qt3D uses. In my main.cpp
I set
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
The context of the QOpenGLWidget
and of Qt3D both reference the same shared context - I verified this by printing both using qDebug
, they are the same object.
Shouldn't this allow me to use the texture from Qt3D?
Or any other suggestions on how to implement such a widget? I just thought this to be the easiest way.
This is what the paintGL
function in my QOpenGLWidget
looks like:
glClearColor(1.0, 1.0, 1.0, 1.0);
glDisable(GL_BLEND);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
d->m_shaderProgram->bind();
{
QMatrix4x4 m;
m.ortho(0, 1, 1, 0, 1.0f, 3.0f);
m.translate(0.0f, 0.0f, -2.0f);
QOpenGLVertexArrayObject::Binder vaoBinder(&d->m_vao);
d->m_shaderProgram->setUniformValue("matrix", m);
glBindTexture(GL_TEXTURE_2D, d->m_colorTexture->handle().toUInt());
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
d->m_shaderProgram->release();
m_colorTexture
is the QTexture2D
that is attached to the QRenderTargetOutput
/QRenderTarget
that Qt3D renders the scene offscreen to.
I have a QFrameAction
in place to trigger draw updates on the QOpenGLWidget
:
connect(d->m_frameAction, &Qt3DLogic::QFrameAction::triggered, this, &Qt3DWidget::paintGL);
I have verified that this indeed calls the paintGL
function. So every time I draw the QOpenGLWidget
, the frame should be ready and present in the texture.
I've also tried to replace the m_colorTexture
with a QSharedGLTexture
. Then I created this texture with the context of the QOpenGLWidget
like this
m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
m_texture->setFormat(QOpenGLTexture::RGBA8_UNorm);
// w and h are width and height of the widget
m_texture->setSize(w, h);
// m_colorTexture is the QSharedGLTexture
m_colorTexture->setTextureId(m_texture->textureId());
In the resizeEvent
function of the QOpenGLWdiget
I set the appropriate size on this texture and also on all offscreen resources of Qt3D. This also shows just a black screen. Placing qDebug() << glGetError();
directly after binding the texture simply shows 0
every time, so I assume that there aren't any errors.
The code can be found here in my GitHub project.
Update (10th May 2021, since I stumbled upon my answer again):
My Qt3DWidget implementation works perfectly now, the issue was that I had to call update()
when the frame action was triggered instead of paintGL
(duh, silly me, I actually know that).
Although I didn't find an exact solution to my question I'll post an answer here since I succeeded in creating a Qt3D widget.
The code can be found here. It's not the cleanest solution because I think it should be possible to use the shared texture somehow. Instead, now I'm setting the QOpenGLWidget
's context on Qt3D for which I have to use Qt3D's private classes. This means that Qt3D draws directly onto the frame buffer bound by the OpenGL widget. Unfortunately, now the widget has to be the render driver and performs manual updates on the QAspectEngine
by calling processFrame
. Ideally, I would have liked to leave all processing loops to Qt3D but at least the widget works now as it is.
Edit:
I found an example for QSharedGLTexture
in the manual tests here. It works the other way round, i.e. OpenGL renders to the texture and Qt3D uses it so I assume it should be possible to inverse the direction. Unfortunately, QSharedGLTexture
seems to be a bit unstable as resizing the OpenGL window sometimes crashes the app. That's why I'll stick with my solution for now. But if anyone has news regarding this issue feel free to post an answer!