2
votes

Goal

I'd like to implement an actual widget for Qt3D since QWidget::createWindowContainer just doesn't cut it for me.


Problem Description

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.


Implementation Details / What I've tried so far

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.

1
I developed quite big QML/Qt3D app with this: May this work for you? ` #include <QGuiApplication> #include <QQmlApplicationEngine> int main(int argc, char* argv[]) { QApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/res/main.qml"))); return app.exec(); } import QtQuick 2.8 import QtQuick.Controls 2.3 import QtQuick.Scene3D 2.0 ApplicationWindow { Scene3D { } }`Alexander V
Thanks for your comment. You're right, actually I was already thinking about switching to QML but the rest of my app (the one that I'm actually developing) is not in QML so I would have to adapt a lot - and I thought that implementing a Qt3D widget wouldn't take so long. Also, I need the 3D widget to be moveable by mouse (similarly to dragging a square around in MS Paint, I hope you get what I mean).Florian Blume
Hybrid mode app sounds feasible. Most of my code is in C++, though. But QML part simplifies things a lot.Alexander V
How about doc.qt.io/archives/qt-5.5/qt3d-qwindow.html Not much info provided though.Alexander V
The thing about the window is that it implements QSurface (because QWindow does and Qt3DWindow is a subclass). Now, I thought about sublcassing QWidget and QSurface and pass the surface to the QRenderSurfaceSelector of Qt3D but I don't actually know how to implement it (e.g. a function that returns QPlatformSurface *) and Qt3D internally expects a QWindow or a QOffscreenSurface, i.e. I would have to adapt a lot of Qt3D code which makes in infeasible.Florian Blume

1 Answers

1
votes

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!