Gráfico de escena - OpenGL bajo QML
Muestra cómo renderizar OpenGL bajo una escena Qt Quick.

El ejemplo OpenGL bajo QML muestra cómo una aplicación puede hacer uso de la señal QQuickWindow::beforeRendering() para dibujar contenido OpenGL personalizado bajo una escena Qt Quick. Esta señal se emite al comienzo de cada fotograma, antes de que el gráfico de la escena comience su renderización, por lo que cualquier llamada de dibujo OpenGL que se realice como respuesta a esta señal, se apilará bajo los elementos Qt Quick.
Como alternativa, las aplicaciones que deseen renderizar contenido OpenGL encima de la escena Qt Quick, pueden hacerlo conectándose a la señal QQuickWindow::afterRendering().
En este ejemplo, también veremos cómo es posible tener valores expuestos a QML que afecten al renderizado OpenGL. Animamos el valor umbral usando un NumberAnimation en el archivo QML y este valor es usado por el programa shader OpenGL que dibuja las ardillas.
El ejemplo es equivalente en la mayoría de los aspectos a los ejemplos Direct3D 11 Under QML, Metal Under QML, y Vulkan Under QML, todos ellos renderizan el mismo contenido personalizado, sólo que a través de diferentes APIs nativas.
class Squircle : public QQuickItem { Q_OBJECT Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged) QML_ELEMENT public: Squircle(); qreal t() const { return m_t; } void setT(qreal t); signals: void tChanged(); public slots: void sync(); void cleanup(); private slots: void handleWindowChanged(QQuickWindow *win); private: void releaseResources() override; qreal m_t; SquircleRenderer *m_renderer; };
En primer lugar, necesitamos un objeto que podamos exponer a QML. Esta es una subclase de QQuickItem para que podamos acceder fácilmente a QQuickItem::window(). Lo exponemos a QML utilizando la macro QML_ELEMENT.
class SquircleRenderer : public QObject, protected QOpenGLFunctions { Q_OBJECT public: ~SquircleRenderer(); void setT(qreal t) { m_t = t; } void setViewportSize(const QSize &size) { m_viewportSize = size; } void setWindow(QQuickWindow *window) { m_window = window; } public slots: void init(); void paint(); private: QSize m_viewportSize; qreal m_t = 0.0; QOpenGLShaderProgram *m_program = nullptr; QQuickWindow *m_window = nullptr; QOpenGLBuffer m_vbo; };
Luego necesitamos un objeto que se encargue del renderizado. Esta instancia necesita estar separada de QQuickItem porque el elemento vive en el hilo GUI y el renderizado potencialmente ocurre en el hilo de renderizado. Como queremos conectarnos a QQuickWindow::beforeRendering(), hacemos del renderizador un QObject. El renderizador contiene una copia de todo el estado que necesita, independiente del hilo GUI.
Nota: No caigas en la tentación de fusionar los dos objetos en uno. QQuickItems puede ser borrado en el thread GUI mientras el thread de renderizado está renderizando.
Pasemos a la implementación.
Squircle::Squircle() : m_t(0) , m_renderer(nullptr) { connect(this, &QQuickItem::windowChanged, this, &Squircle::handleWindowChanged); }
El constructor de la clase Squircle simplemente inicializa los valores y se conecta a la señal de cambio de ventana que usaremos para preparar nuestro renderizador.
void Squircle::handleWindowChanged(QQuickWindow *win) { if (win) { connect(win, &QQuickWindow::beforeSynchronizing, this, &Squircle::sync, Qt::DirectConnection); connect(win, &QQuickWindow::sceneGraphInvalidated, this, &Squircle::cleanup, Qt::DirectConnection);
Una vez que tenemos una ventana, nos conectamos a la señal QQuickWindow::beforeSynchronizing() que usaremos para crear el renderizador y copiar el estado en él de forma segura. También nos conectamos a la señal QQuickWindow::sceneGraphInvalidated() para manejar la limpieza del renderizador.
Nota: Dado que el objeto Squircle tiene afinidad con el hilo GUI y las señales se emiten desde el hilo de renderizado, es crucial que las conexiones se realicen con Qt::DirectConnection. De lo contrario, las ranuras se invocarán en el subproceso equivocado sin contexto OpenGL presente.
// Ensure we start with cleared to black. The squircle's blend mode relies on this. win->setColor(Qt::black); } }
El comportamiento por defecto del gráfico de escena es borrar el framebuffer antes de renderizar. Esto está bien ya que insertaremos nuestro propio código de renderizado después de que este clear sea puesto en cola. Asegúrate, sin embargo, de que borramos el color deseado (negro).
void Squircle::sync() { if (!m_renderer) { m_renderer = new SquircleRenderer(); connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::init, Qt::DirectConnection); connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::paint, Qt::DirectConnection); } m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio()); m_renderer->setT(m_t); m_renderer->setWindow(window()); }
Usamos la función sync() para inicializar el renderizador y copiar el estado de nuestro item en el renderizador. Cuando se crea el renderizador, también conectamos las señales QQuickWindow::beforeRendering() y QQuickWindow::beforeRenderPassRecording() a las ranuras init() y paint() del renderizador.
Nota: La señal QQuickWindow::beforeSynchronizing() se emite en el hilo de renderizado mientras el hilo GUI está bloqueado, por lo que es seguro simplemente copiar el valor sin ninguna protección adicional.
void Squircle::cleanup() { delete m_renderer; m_renderer = nullptr; } class CleanupJob : public QRunnable { public: CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { } void run() override { delete m_renderer; } private: SquircleRenderer *m_renderer; }; void Squircle::releaseResources() { window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage); m_renderer = nullptr; } SquircleRenderer::~SquircleRenderer() { delete m_program; }
En la función cleanup() borramos el renderizador que a su vez limpia sus propios recursos. Esto se complementa con la reimplementación de QQuickWindow::releaseResources() ya que la simple conexión a la señal sceneGraphInvalidated() no es suficiente por sí sola para manejar todos los casos.
void Squircle::setT(qreal t) { if (t == m_t) return; m_t = t; emit tChanged(); if (window()) window()->update(); }
Cuando el valor de t cambia, llamamos a QQuickWindow::update() en lugar de QQuickItem::update() porque el primero forzará a redibujar toda la ventana, incluso cuando el gráfico de escena no haya cambiado desde el último fotograma.
void SquircleRenderer::init() { if (!m_program) { QSGRendererInterface *rif = m_window->rendererInterface(); Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::OpenGL); initializeOpenGLFunctions(); const float values[] = { -1, -1, 1, -1, -1, 1, 1, 1 }; m_vbo.create(); m_vbo.bind(); m_vbo.allocate(values, sizeof(values)); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr); m_program = new QOpenGLShaderProgram(); m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, "attribute highp vec4 vertices;" "varying highp vec2 coords;" "void main() {" " gl_Position = vertices;" " coords = vertices.xy;" "}"); m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, "uniform lowp float t;" "varying highp vec2 coords;" "void main() {" " lowp float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.));" " i = smoothstep(t - 0.8, t + 0.8, i);" " i = floor(i * 20.) / 20.;" " gl_FragColor = vec4(coords * .5 + .5, i, i);" "}"); m_program->bindAttributeLocation("vertices", 0); m_program->link(); } }
En la función init() del SquircleRenderer comenzamos inicializando el programa de sombreado si aún no se ha hecho. El contexto OpenGL es actual en el thread cuando se invoca el slot.
void SquircleRenderer::paint() { // Play nice with the RHI. Not strictly needed when the scenegraph uses // OpenGL directly. m_window->beginExternalCommands(); m_vbo.bind(); m_program->bind(); m_program->setUniformValue("t", (float)m_t); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr); glViewport(0, 0, m_viewportSize.width(), m_viewportSize.height()); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(0); m_program->release(); m_window->endExternalCommands(); }
Utilizamos el programa de sombreado para dibujar la ardilla en paint().
int main(int argc, char **argv) { QGuiApplication app(argc, argv); QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); QQuickView view; view.setResizeMode(QQuickView::SizeRootObjectToView); view.setSource(QUrl("qrc:///scenegraph/openglunderqml/main.qml")); view.show(); return QGuiApplication::exec(); }
La función main() de la aplicación instancia un QQuickView y lanza el archivo main.qml.
import QtQuick import OpenGLUnderQML Item { width: 320 height: 480 Squircle { SequentialAnimation on t { NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad } NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad } loops: Animation.Infinite running: true } }
Importamos el tipo QML Squircle con el nombre que registramos en la función main(). A continuación, lo instanciamos y creamos un NumberAnimation en ejecución en su propiedad t.
Rectangle { color: Qt.rgba(1, 1, 1, 0.7) radius: 10 border.width: 1 border.color: "white" anchors.fill: label anchors.margins: -10 } Text { id: label color: "black" wrapMode: Text.WordWrap text: qsTr("The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML") anchors.right: parent.right anchors.left: parent.left anchors.bottom: parent.bottom anchors.margins: 20 } }
A continuación superponemos un breve texto descriptivo, para que sea claramente visible que en realidad estamos renderizando OpenGL bajo nuestra escena Qt Quick.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.