Qt Quick Szenengraph
Der Szenenbaum in Qt Quick
Qt Quick 2 nutzt einen speziellen Szenengraphen, der anschließend über eine Grafik-API wie OpenGL ES, OpenGL, Vulkan, Metal oder Direct 3D durchlaufen und gerendert wird. Die Verwendung eines Szenengraphen für die Grafik anstelle der herkömmlichen imperativen Zeichensysteme (QPainter und dergleichen) bedeutet, dass die zu rendernde Szene zwischen den Einzelbildern beibehalten werden kann und der vollständige Satz der zu rendernden Primitive bereits vor Beginn des Renderings bekannt ist. Dies eröffnet eine Reihe von Optimierungsmöglichkeiten, wie beispielsweise das Batch-Rendering zur Minimierung von Zustandsänderungen und das Verwerfen verdeckter Primitive.
Nehmen wir zum Beispiel an, eine Benutzeroberfläche enthält eine Liste mit zehn Elementen, wobei jedes Element eine Hintergrundfarbe, ein Symbol und einen Text hat. Bei Verwendung der herkömmlichen Zeichentechniken würde dies zu 30 Zeichenaufrufen und einer ähnlichen Anzahl von Zustandsänderungen führen. Ein Szenengraph hingegen könnte die zu rendernden Primitive so neu anordnen, dass zunächst alle Hintergründe in einem einzigen Aufruf gezeichnet werden, dann alle Symbole und schließlich der gesamte Text, wodurch sich die Gesamtzahl der Zeichenaufrufe auf nur 3 reduziert. Eine solche Bündelung und Reduzierung von Zustandsänderungen kann die Leistung auf manchen Hardwarekomponenten erheblich verbessern.
Der Szenenbaum ist eng mit „ Qt Quick “ 2.0 verknüpft und kann nicht eigenständig verwendet werden. Der Szenenbaum wird von der Klasse „ QQuickWindow “ verwaltet und gerendert, und benutzerdefinierte „Item“-Typen können ihre grafischen Primitive über einen Aufruf von „ QQuickItem::updatePaintNode()“ in den Szenenbaum einfügen.
Der Szenengraph ist eine grafische Darstellung der Item-Szene, einer eigenständigen Struktur, die genügend Informationen enthält, um alle Items zu rendern. Sobald er eingerichtet ist, kann er unabhängig vom Zustand der Items bearbeitet und gerendert werden. Auf vielen Plattformen wird der Szenenbaum sogar in einem eigenen Render-Thread gerendert, während der GUI-Thread den Zustand des nächsten Frames vorbereitet.
Hinweis: Ein Großteil der auf dieser Seite aufgeführten Informationen bezieht sich speziell auf das integrierte Standardverhalten des „ Qt Quick “-Szenengraphen. Bei Verwendung einer alternativen Szenengraph-Anpassung, wie beispielsweise der „ software “-Anpassung, treffen möglicherweise nicht alle Konzepte zu. Weitere Informationen zu den verschiedenen Szenengraph-Anpassungen finden Sie unter „Szenengraph-Anpassungen“.
Qt Quick Struktur des Szenengraphen
Der Szenengraph setzt sich aus einer Reihe vordefinierter Knotentypen zusammen, von denen jeder einem bestimmten Zweck dient. Obwohl wir ihn als Szenengraph bezeichnen, ist die präzisere Bezeichnung „Knotenbaum“. Der Baum wird aus „ QQuickItem “-Typen in der QML-Szene aufgebaut, und intern wird die Szene dann von einem Renderer verarbeitet, der die Szene zeichnet. Die Knoten selbst enthalten weder aktiven Zeichencode noch eine virtuelle „ paint() “-Funktion.
Auch wenn der Knotenbaum größtenteils intern aus den vorhandenen „ Qt Quick “-QML-Typen aufgebaut wird, ist es Benutzern möglich, auch komplette Unterbäume mit eigenem Inhalt hinzuzufügen, einschließlich Unterbäumen, die 3D-Modelle darstellen.
Knoten
Der für Benutzer wichtigste Knoten ist der „ QSGGeometryNode “. Er dient dazu, benutzerdefinierte Grafiken durch die Festlegung ihrer Geometrie und ihres Materials zu definieren. Die Geometrie wird mithilfe von ` QSGGeometry ` definiert und beschreibt die Form oder das Netz der grafischen Primitive. Dabei kann es sich um eine Linie, ein Rechteck, ein Polygon, mehrere nicht miteinander verbundene Rechtecke oder ein komplexes 3D-Netz handeln. Das Material definiert, wie die Pixel in dieser Form gefüllt werden.
Ein Knoten kann eine beliebige Anzahl von untergeordneten Knoten haben, und Geometrieknoten werden so gerendert, dass sie in der Reihenfolge ihrer untergeordneten Knoten erscheinen, wobei die übergeordneten Knoten hinter ihren untergeordneten Knoten liegen.
Hinweis: Dies sagt nichts über die tatsächliche Renderreihenfolge im Renderer aus. Es wird lediglich die visuelle Ausgabe garantiert.
Die verfügbaren Knoten sind:
Implementiert die Clipping-Funktionalität im Szenenbaum | |
Wird für alle gerenderten Inhalte im Szenenbaum verwendet | |
Die Basisklasse für alle Knoten im Szenenbaum | |
Wird verwendet, um die Deckkraft von Knoten zu ändern | |
Implementiert Transformationen im Szenengraphen |
Benutzerdefinierte Knoten werden dem Szenenbaum hinzugefügt, indem man ` QQuickItem::updatePaintNode()` unterklassifiziert und das Flag „ QQuickItem::ItemHasContents “ setzt.
Warnung: Es ist von entscheidender Bedeutung, dass native Grafikoperationen (OpenGL, Vulkan, Metal usw.) und die Interaktion mit dem Szenengraphen ausschließlich im Render-Thread stattfinden, vor allem während des Aufrufs von `updatePaintNode()`. Als Faustregel gilt, innerhalb der Funktion ` QQuickItem::updatePaintNode()` nur Klassen mit dem Präfix „QSG“ zu verwenden.
Weitere Details finden Sie unter „Szenengraph – Benutzerdefinierte Geometrie“.
Vorverarbeitung
Knoten verfügen über eine virtuelle Funktion „ QSGNode::preprocess()“, die vor dem Rendern des Szenengraphen aufgerufen wird. Unterklassen von Knoten können das Flag „ QSGNode::UsePreprocess “ setzen und die Funktion „ QSGNode::preprocess()“ überschreiben, um die abschließenden Vorbereitungen für ihren Knoten vorzunehmen. Dazu gehören beispielsweise die Aufteilung einer Bézier-Kurve in die für den aktuellen Skalierungsfaktor richtige Detailstufe oder die Aktualisierung eines Teils einer Textur.
Knoteneigentum
Die Zuweisung der Eigentumsrechte an den Knoten erfolgt entweder explizit durch den Ersteller oder durch den Szenengraphen durch Setzen des Flags ` QSGNode::OwnedByParent`. Die Zuweisung der Eigentumsrechte an den Szenengraphen ist oft vorzuziehen, da sie die Bereinigung vereinfacht, wenn sich der Szenengraph außerhalb des GUI-Threads befindet.
Materialien
Das Material beschreibt, wie das Innere einer Geometrie in einem „ QSGGeometryNode “ ausgefüllt wird. Es kapselt Grafik-Shader für die Vertex- und Fragment-Stufen der Grafik-Pipeline und bietet große Flexibilität hinsichtlich der Möglichkeiten, die sich damit realisieren lassen, obwohl die meisten Elemente von „ Qt Quick “ selbst nur sehr einfache Materialien verwenden, wie beispielsweise einfarbige oder mit Texturen gefüllte Flächen.
Benutzer, die lediglich eine benutzerdefinierte Schattierung auf einen QML-Elementtyp anwenden möchten, können dies direkt in QML mithilfe des Typs „ ShaderEffect “ tun.
Nachfolgend finden Sie eine vollständige Liste der Materialklassen:
Praktische Methode zum Rendern einfarbiger Geometrie im Szenenbaum | |
Kapseln den Rendering-Zustand für ein Shader-Programm | |
Stellt ein von der Grafik-API unabhängiges Shader-Programm dar | |
Wird in Kombination mit QSGMaterial als eindeutiges Typ-Token verwendet | |
Praktische Möglichkeit, texturierte Geometrie im Szenenbaum zu rendern | |
Bequeme Möglichkeit, texturierte Geometrie im Szenenbaum darzustellen | |
Bequeme Möglichkeit, pro-Vertex-farbige Geometrie im Szenenbaum zu rendern |
Komfort-Knoten
Die Szenengraph-API ist auf niedriger Ebene angesiedelt und legt den Schwerpunkt eher auf Leistung als auf Benutzerfreundlichkeit. Das Schreiben benutzerdefinierter Geometrien und Materialien von Grund auf – selbst der einfachsten – erfordert einen nicht unerheblichen Programmieraufwand. Aus diesem Grund enthält die API einige Komfortklassen, um die gängigsten benutzerdefinierten Knoten sofort verfügbar zu machen.
- QSGSimpleRectNode - eine Unterklasse von ` QSGGeometryNode `, die eine rechteckige Geometrie mit einem einfarbigen Material definiert.
- QSGSimpleTextureNode - eine Unterklasse von ` QSGGeometryNode `, die eine rechteckige Geometrie mit einem Textur-Material definiert.
Szenengrafik und Rendering
Das Rendering des Szenengraphen erfolgt intern in der Klasse „ QQuickWindow “, und es gibt keine öffentliche API, um darauf zuzugreifen. Es gibt jedoch einige Stellen in der Rendering-Pipeline, an denen der Benutzer Anwendungscode einbinden kann. Dies kann genutzt werden, um benutzerdefinierte Inhalte in den Szenenbaum einzufügen oder beliebige Rendering-Befehle einzufügen, indem die vom Szenenbaum verwendete Grafik-API (OpenGL, Vulkan, Metal usw.) direkt aufgerufen wird. Die Integrationspunkte werden durch die Render-Schleife definiert.
Eine detaillierte Beschreibung der Funktionsweise des Szenengraph-Renderers finden Sie unter „Qt Quick: Standard-Renderer für den Szenengraphen“.
Es stehen zwei Varianten der Render-Schleife zur Verfügung: „ basic “ und „ threaded “. „ basic “ ist single-threaded, während „ threaded “ das Rendern des Szenengraphen in einem eigenen Thread durchführt. Qt versucht, basierend auf der Plattform und gegebenenfalls den verwendeten Grafiktreibern eine geeignete Schleife auszuwählen. Wenn dies nicht zufriedenstellend ist oder zu Testzwecken, kann die Umgebungsvariable „ QSG_RENDER_LOOP “ verwendet werden, um die Verwendung einer bestimmten Schleife zu erzwingen. Um zu überprüfen, welche Render-Schleife verwendet wird, aktivieren Sie die Option „ qt.scenegraph.general “ unter „ logging category “.
Threaded-Render-Loop („threaded“)
Bei vielen Konfigurationen erfolgt das Rendern des Szenengraphen in einem dedizierten Render-Thread. Dies geschieht, um die Parallelität von Multi-Core-Prozessoren zu erhöhen und Wartezeiten – wie beispielsweise das Warten auf einen blockierenden Swap-Buffer-Aufruf – besser zu nutzen. Dies bietet erhebliche Leistungsverbesserungen, schränkt jedoch ein, wo und wann Interaktionen mit dem Szenengraphen stattfinden können.
Im Folgenden wird in groben Zügen beschrieben, wie ein Frame mit der Threaded-Render-Loop und OpenGL gerendert wird. Abgesehen von den Besonderheiten des OpenGL-Kontexts sind die Schritte auch bei anderen Grafik-APIs identisch.

- In der QML-Szene tritt eine Änderung auf, wodurch `
QQuickItem::update()` aufgerufen wird. Dies kann beispielsweise durch eine Animation oder eine Benutzereingabe verursacht werden. Ein Ereignis wird an den Render-Thread gesendet, um ein neues Bild zu initiieren. - Der Render-Thread bereitet das Zeichnen eines neuen Frames vor und löst eine Blockierung des GUI-Threads aus.
- Während der Render-Thread den neuen Frame vorbereitet, ruft der GUI-Thread ` QQuickItem::updatePolish()` auf, um letzte Anpassungen an den Elementen vorzunehmen, bevor diese gerendert werden.
- Der GUI-Thread wird blockiert.
- Das Signal ` QQuickWindow::beforeSynchronizing()` wird ausgesendet. Anwendungen können (mithilfe von ` Qt::DirectConnection`) direkte Verbindungen zu diesem Signal herstellen, um alle erforderlichen Vorbereitungen vor dem Aufruf von ` QQuickItem::updatePaintNode()` durchzuführen.
- Synchronisierung des QML-Zustands mit dem Szenengraphen. Dies erfolgt durch den Aufruf der Funktion ` QQuickItem::updatePaintNode()` für alle Elemente, die sich seit dem vorherigen Frame geändert haben. Dies ist der einzige Zeitpunkt, zu dem die QML-Elemente und die Knoten im Szenengraphen miteinander interagieren.
- Der GUI-Thread-Block wird freigegeben.
- Der Szenengraph wird gerendert:
- Das Signal „ QQuickWindow::beforeRendering()“ wird ausgesendet. Anwendungen können direkte Verbindungen (mithilfe von „ Qt::DirectConnection “) zu diesem Signal herstellen, um benutzerdefinierte Grafik-API-Aufrufe zu verwenden, die dann visuell unter der QML-Szene eingeblendet werden.
- Bei Elementen, für die „ QSGNode::UsePreprocess “ angegeben wurde, wird die Funktion „ QSGNode::preprocess()“ aufgerufen.
- Der Renderer verarbeitet die Knoten.
- Der Renderer generiert Zustände und zeichnet Zeichenaufrufe für die verwendete Grafik-API auf.
- Das Signal „ QQuickWindow::afterRendering()“ wird ausgesendet. Anwendungen können (mithilfe von „ Qt::DirectConnection “) direkte Verbindungen zu diesem Signal herstellen, um benutzerdefinierte Grafik-API-Aufrufe auszuführen, die dann visuell über der QML-Szene überlagert werden.
- Der Frame ist nun bereit. Die Puffer werden ausgetauscht (OpenGL) oder ein „present“-Befehl wird aufgezeichnet und die Befehlspuffer werden an eine Grafikwarteschlange übergeben (Vulkan, Metal). „ QQuickWindow::frameSwapped()“ wird ausgelöst.
- Während der Render-Thread rendert, kann die GUI Animationen vorantreiben, Ereignisse verarbeiten usw.
Der Threaded-Renderer wird derzeit standardmäßig unter Windows mit Direct3D 11 und mit OpenGL bei Verwendung von opengl32.dll, unter Linux (mit Ausnahme von Mesa llvmpipe), unter macOS mit Metal, auf mobilen Plattformen sowie unter Embedded Linux mit EGLFS und plattformunabhängig mit Vulkan verwendet. All dies kann sich in zukünftigen Versionen ändern. Es ist jederzeit möglich, die Verwendung des Threaded-Renderers zu erzwingen, indem man „ QSG_RENDER_LOOP=threaded “ in der Umgebung setzt.
Nicht-threadbasierte Render-Schleife („basic“)
Die nicht-threadbasierte Render-Schleife wird derzeit standardmäßig unter Windows mit OpenGL verwendet, wenn nicht die systemeigene opengl32.dll zum Einsatz kommt, sowie unter macOS mit OpenGL, WebAssembly und unter Linux mit bestimmten Treibern. Im letzteren Fall handelt es sich dabei größtenteils um eine Vorsichtsmaßnahme, da nicht alle Kombinationen aus OpenGL-Treibern und Fenstersystemen getestet wurden.
Unter macOS und OpenGL wird die Thread-basierte Render-Schleife nicht unterstützt, wenn mit Xcode 10 (10.14 SDK) oder später kompiliert wird, da hierbei unter macOS 10.14 die Verwendung von Layer-Backed-Ansichten aktiviert ist. Sie können mit Xcode 9 (10.13 SDK) kompilieren, um die Layer-Unterstützung zu deaktivieren; in diesem Fall ist die Thread-basierte Render-Schleife verfügbar und wird standardmäßig verwendet. Bei Metal gibt es keine derartige Einschränkung.
Die Thread-basierte Render-Schleife wird unter WebAssembly nicht unterstützt, da die Webplattform nur eingeschränkte Unterstützung für die Verwendung von WebGL auf anderen Threads als dem Haupt-Thread sowie für das Blockieren des Haupt-Threads bietet.
Selbst bei Verwendung der nicht-threadbasierten Render-Schleife sollten Sie Ihren Code so schreiben, als würden Sie den threadbasierten Renderer verwenden, da der Code andernfalls nicht portierbar ist.
Im Folgenden finden Sie eine vereinfachte Darstellung der Abfolge der Frame-Rendering-Schritte im nicht-threadbasierten Renderer.

Animationen steuern
Worauf bezieht sich „ Advance Animations “ in den obigen Diagrammen?
Standardmäßig wird eine „ Qt Quick “-Animation (wie z. B. „ NumberAnimation “) vom Standard-Animationstreiber gesteuert. Dieser stützt sich auf grundlegende Systemtimer wie „ QObject::startTimer()“. Der Timer läuft in der Regel mit einem Intervall von 16 Millisekunden. Dies ist zwar nie ganz exakt und hängt zudem von der Genauigkeit der Timer der zugrunde liegenden Plattform ab, hat jedoch den Vorteil, dass es unabhängig vom Rendering ist. Es liefert einheitliche Ergebnisse, unabhängig von der Bildwiederholfrequenz des Bildschirms und davon, ob die Synchronisation mit dem vertikalen Synchronisationssignal des Bildschirms aktiv ist oder nicht. So funktionieren Animationen mit der „ basic “-Render-Schleife.
Um unabhängig vom Design der Render-Schleife (sei es single-threaded oder mit mehreren Threads) genauere Ergebnisse mit weniger Rucklern auf dem Bildschirm zu erzielen, kann eine Render-Schleife beschließen, einen eigenen benutzerdefinierten Animations-Treiber zu installieren und die Steuerung der „ advancing “ selbst in die Hand zu nehmen, ohne sich auf Timer zu verlassen.
Genau das setzt die Render-Schleife von threaded um. Tatsächlich richtet sie nicht nur einen, sondern zwei Animationstreiber ein: einen auf dem GUI-Thread (zur Steuerung regulärer Animationen, wie z. B. NumberAnimation) und einen auf dem Render-Thread (zur Steuerung von Animationen im Render-Thread, d. h. der Animator -Typen, wie z. B. OpacityAnimator oder XAnimator). Beide werden während der Vorbereitung eines Frames vorangetrieben, d. h., Animationen werden nun mit dem Rendering synchronisiert. Dies ist sinnvoll, da die Darstellung durch den zugrunde liegenden Grafikstack an die vertikale Synchronisation des Bildschirms angepasst wird.
Daher gibt es im obigen Diagramm zur Render-Schleife von „ threaded “ auf beiden Threads einen expliziten Schritt „ Advance animations “. Für den Render-Thread ist dies trivial: Da der Thread an die vertikale Synchronisation (Vsync) gedrosselt wird, liefert das Vorantreiben von Animationen (für „ Animator “-Typen) in jedem Frame, als wären 16,67 Millisekunden verstrichen, genauere Ergebnisse als das Verlassen auf einen Systemtimer. (Bei einer Drosselung auf das Vsync-Timing – das bei einer Bildwiederholfrequenz von 60 Hz 1000/60 -Millisekunden beträgt – kann man davon ausgehen, dass seit der Ausführung derselben Operation für den vorherigen Frame ungefähr so viel Zeit vergangen ist.)
Der gleiche Ansatz funktioniert auch für Animationen im GUI- (Haupt-)Thread: Aufgrund der notwendigen Synchronisation der Daten zwischen dem GUI- und dem Render-Thread wird der GUI-Thread effektiv auf die gleiche Rate wie der Render-Thread gedrosselt, profitiert dabei jedoch weiterhin von einem geringeren Arbeitsaufwand. Da ein Großteil der Vorbereitungen für das Rendering nun auf den Render-Thread verlagert wird, bleibt mehr Spielraum für die Anwendungslogik.
Während in den obigen Beispielen 60 Frames pro Sekunde verwendet wurden, ist „ Qt Quick “ auch für andere Bildwiederholraten ausgelegt: Die Bildwiederholrate wird über den „ QScreen “ und die Plattform abgefragt. Bei einem 144-Hz-Bildschirm beträgt das Intervall beispielsweise 6,94 ms. Gleichzeitig kann genau dies zu Problemen führen, wenn die vsync-basierte Drosselung nicht wie erwartet funktioniert, denn wenn die Einschätzung der Render-Schleife nicht mit der Realität übereinstimmt, kommt es zu einem falschen Animationsrhythmus.
Hinweis: Ab Qt 6.5 bietet die Threaded-Render-Loop die Möglichkeit, einen alternativen Animations-Treiber zu aktivieren, der sich ausschließlich auf die verstrichene Zeit stützt (QElapsedTimer). Um dies zu aktivieren, setzen Sie die Umgebungsvariable ` QSG_USE_SIMPLE_ANIMATION_DRIVER ` auf einen Wert ungleich Null. Dies hat den Vorteil, dass keine Infrastruktur für den Fallback auf einen „ QTimer “ bei mehreren Fenstern benötigt wird, keine Heuristiken erforderlich sind, um festzustellen, ob vsync-basierte Drosselung fehlt oder fehlerhaft ist, die Kompatibilität mit jeglicher Art von zeitlichen Abweichungen bei der vsync-Drosselung gewährleistet ist und nicht an die Bildwiederholfrequenz des Hauptbildschirms gebunden zu sein, wodurch es in Mehrbildschirm-Konfigurationen potenziell besser funktioniert. Außerdem werden Animationen im Render-Thread (vom Typ „ Animator “) korrekt ausgeführt, selbst wenn die vsync-basierte Drosselung fehlerhaft oder deaktiviert ist. Andererseits können Animationen bei diesem Ansatz als weniger flüssig empfunden werden. Aus Kompatibilitätsgründen wird diese Funktion derzeit als Opt-in-Funktion angeboten.
Zusammenfassend lässt sich sagen, dass die „ threaded “-Render-Schleife voraussichtlich flüssigere Animationen mit weniger Ruckeln liefert, sofern die folgenden Bedingungen erfüllt sind:
- Es befindet sich genau ein Fenster (wie bei „ QQuickWindow “) auf dem Bildschirm.
- Die VSync-basierte Drosselung funktioniert wie erwartet mit dem zugrunde liegenden Grafik- und Anzeigestack.
Was passiert, wenn kein oder mehr als ein Fenster sichtbar ist?
Wenn kein rendierbares Fenster vorhanden ist, beispielsweise weil unser „ QQuickWindow “ minimiert (Windows) oder vollständig verdeckt (macOS) ist, können wir keine Frames darstellen und uns daher nicht darauf verlassen, dass der Thread im Gleichschritt mit der Bildwiederholfrequenz des Bildschirms „arbeitet“. In diesem Fall wechselt die „ threaded “-Render-Schleife automatisch zu einem systemtimerbasierten Ansatz zur Steuerung von Animationen, d. h. sie wechselt vorübergehend zu dem Mechanismus, den die „ basic “-Schleife verwenden würde.
Das Gleiche gilt, wenn mehr als eine „ QQuickWindow “-Instanz auf dem Bildschirm angezeigt wird. Das oben dargestellte Modell zur Fortführung von Animationen auf dem GUI-Thread, das durch dessen Synchronisation mit dem Render-Thread ermöglicht wird, ist nicht mehr zufriedenstellend, da es nun mehrere Synchronisationspunkte mit mehreren Render-Threads gibt (einen pro Fenster). Auch hier wird ein Rückgriff auf den systemtimerbasierten Ansatz notwendig, da die Dauer und Häufigkeit der Blockierung des GUI-Threads nun von einer Reihe von Faktoren abhängt, darunter dem Inhalt der Fenster (Werden sie animiert? Wie oft werden sie aktualisiert?) sowie vom Verhalten des Grafikstacks (Wie genau geht dieser mit zwei oder mehr Threads um, die mit „Wait-for-Vsync“ darstellen?). Da wir nicht garantieren können, dass die Darstellung stabil und plattformübergreifend auf die Darstellungsrate des Fensters gedrosselt wird (um welches Fenster würde es sich überhaupt handeln?), kann die Fortführung von Animationen nicht auf dem Rendering basieren.
Dieser Wechsel der Animationsmechanismen erfolgt für die Anwendungen transparent.
Was ist, wenn die vsync-basierte Drosselung nicht funktioniert, global deaktiviert ist oder die Anwendung sie selbst deaktiviert hat?
Die „ threaded “-Render-Schleife stützt sich bei der Drosselung auf die Grafik-API-Implementierung und/oder das Fenstersystem, beispielsweise durch die Anforderung eines Swap-Intervalls von 1 bei OpenGL (GLX, EGL, WGL), den Aufruf von `Present()` mit einem Intervall von 1 bei Direct 3D oder die Verwendung des Präsentationsmodus „ FIFO “ bei Vulkan.
Einige Grafiktreiber ermöglichen es Benutzern, diese Einstellung zu überschreiben und zu deaktivieren, wobei die Anforderung von Qt ignoriert wird. Ein Beispiel hierfür wäre eine systemweite Systemsteuerung des Grafiktreibers, die es erlaubt, die Einstellungen der Anwendung in Bezug auf Vsync zu überschreiben. Es kann auch vorkommen, dass ein Grafikstack keine ordnungsgemäße vsync-basierte Drosselung bereitstellen kann, was in einigen virtuellen Maschinen der Fall sein kann (hauptsächlich aufgrund der Verwendung einer auf Software-Rasterisierung basierenden Implementierung von OpenGL oder Vulkan).
Ohne eine Blockierung im Swap-/Present-Vorgang (oder einem anderen Grafikvorgang) würde eine solche Render-Schleife Animationen zu schnell abspielen. Bei der „ basic “-Render-Schleife wäre dies kein Problem, da diese stets auf System-Timer zurückgreift. Bei „ threaded “ kann das Verhalten je nach Qt-Version variieren:
- Wenn bekannt ist, dass ein System keine vsync-basierte Drosselung bereitstellen kann, bestand vor Qt 6.4 die einzige Möglichkeit darin, die „
basic“-Render-Schleife zu verwenden, indem „QSG_RENDER_LOOP=basic“ vor dem Ausführen der Anwendung manuell in der Umgebung gesetzt wurde. - Ab Qt 6.4 kann das Problem ebenfalls behoben werden, indem entweder die Umgebungsvariable „
QSG_NO_VSYNC“ auf einen Wert ungleich Null gesetzt wird oder die Methode „ QSurfaceFormat::swapInterval()“ des Fensters auf „0“ gesetzt wird: Durch die explizite Anforderung, die vsync-basierte Blockierung zu deaktivieren – unabhängig davon, ob diese Anforderung in der Praxis tatsächlich Wirkung zeigt –, kann die „threaded“-Render-Schleife im weiteren Sinne erkennen, dass es sinnlos ist, sich bei der Steuerung von Animationen auf vsync zu verlassen, und weicht auf die Verwendung von Systemtimern zurück, genau wie dies bei mehr als einem Fenster der Fall wäre. - Noch besser: Ab Qt 6.4 versucht der Szenengraf, mithilfe einfacher Heuristiken zu erkennen, dass die Frames „zu schnell“ dargestellt werden, und wechselt bei Bedarf automatisch zu Systemtimern. Das bedeutet, dass in den meisten Fällen kein Eingreifen erforderlich ist und Anwendungen Animationen wie erwartet ausführen, selbst wenn die Standard-Render-Schleife „
threaded“ ist. Während dies für Anwendungen transparent abläuft, ist es für Fehlerbehebung und Entwicklungszwecke nützlich zu wissen, dass dies mit einer „"Window 0x7ffc8489c3d0 is determined to have broken vsync throttling ..."“-Meldung protokolliert wird, die ausgegeben wird, wenn „QSG_INFO“ oder „qt.scenegraph.general“ aktiviert ist. Diese Methode hat den Nachteil, dass sie erst nach einer kleinen Anzahl von Frames aktiv wird, da zunächst Daten zur Auswertung gesammelt werden müssen. Das bedeutet, dass die Anwendung beim Öffnen eines „ QQuickWindow “ möglicherweise für einen kurzen Zeitraum weiterhin übermäßig schnelle Animationen anzeigt. Zudem erfasst sie möglicherweise nicht alle möglichen Situationen, in denen die Vsync-Synchronisation unterbrochen ist.
Beachten Sie jedoch, dass dies aufgrund der Konzeption bei Animationen des Render-Threads (vom Typ „ Animator “) keine Hilfe darstellt. Ohne eine auf Vsync basierende Blockierung läuft „ animators “ standardmäßig falsch ab – schneller als erwartet –, selbst wenn die Workarounds für reguläre „ animations “ aktiviert sind. Sollte dies zu einem Problem werden, sollten Sie die Verwendung des alternativen Animations-Treibers in Betracht ziehen, indem Sie „ QSG_USE_SIMPLE_ANIMATION_DRIVER “ setzen.
Hinweis: Beachten Sie , dass die Logik der Render-Schleife und die Ereignisverarbeitung im GUI- (Haupt-)Thread nicht unbedingt unbegrenzt sind, selbst wenn das Warten auf Vsync deaktiviert ist: Beide Render-Schleifen planen Aktualisierungen für Fenster über ` QWindow::requestUpdate()` ein. Dies wird auf den meisten Plattformen durch einen 5-ms-GUI-Thread-Timer gestützt, um Zeit für die Ereignisverarbeitung zu schaffen. Auf einigen Plattformen, z. B. macOS, werden plattformspezifische APIs (wie z. B. CVDisplayLink) verwendet, um über den geeigneten Zeitpunkt zur Vorbereitung eines neuen Frames informiert zu werden, der wahrscheinlich in irgendeiner Form an den Vsync des Displays gekoppelt ist. Dies kann bei Benchmarks und ähnlichen Situationen relevant sein. Für Anwendungen und Tools, die Low-Level-Benchmarks durchführen möchten, kann es vorteilhaft sein, die Umgebungsvariable ` QT_QPA_UPDATE_IDLE_TIME ` auf ` 0 ` zu setzen, um möglicherweise die Leerlaufzeit im GUI-Thread zu reduzieren. Für den normalen Anwendungsbetrieb sollten die Standardeinstellungen in den meisten Fällen ausreichend sein.
Hinweis: Im Zweifelsfall sollten Sie die Protokollierungskategorien „ qt.scenegraph.general “ und „ qt.scenegraph.time.renderloop “ zur Fehlerbehebung aktivieren, da diese möglicherweise Hinweise darauf liefern, warum das Rendering und die Animationen nicht mit der erwarteten Geschwindigkeit ablaufen.
Benutzerdefinierte Steuerung des Renderings mit QQuickRenderControl
Bei Verwendung von ` QQuickRenderControl` wird die Verantwortung für die Steuerung der Render-Schleife an die Anwendung übertragen. In diesem Fall wird keine integrierte Render-Schleife verwendet. Stattdessen obliegt es der Anwendung, die Schritte „Polish“, „Synchronize“ und „Rendering“ zum geeigneten Zeitpunkt aufzurufen. Es ist möglich, entweder ein Thread-basiertes oder ein nicht-Thread-basiertes Verhalten zu implementieren, ähnlich den oben gezeigten Beispielen.
Darüber hinaus können Anwendungen ihren eigenen `QAnimationDriver` in Kombination mit ` QQuickRenderControl` implementieren und installieren. Dies ermöglicht die vollständige Kontrolle über die Steuerung von ` Qt Quick `-Animationen, was insbesondere für Inhalte wichtig sein kann, die nicht auf dem Bildschirm angezeigt werden und keinen Bezug zur Darstellungsrate haben, da die jeweiligen Frames gar nicht dargestellt werden. Dies ist optional; standardmäßig werden Animationen auf Basis des Systemtimers fortgesetzt.
Erweiterung des Szenengraphen um QRhi-basiertes und natives 3D-Rendering
Der Szenengraph bietet drei Methoden zur Integration von anwendungsseitigen Grafikbefehlen:
- Das direkte Ausführen von Befehlen auf Basis von „ QRhi “ oder von OpenGL-, Vulkan-, Metal- und Direct3D-Befehlen entweder vor oder nach dem eigenen Rendering des Szenengraphen. Dadurch wird dem Haupt-Render-Durchlauf eine Reihe von Draw-Calls vorangestellt oder angehängt. Es wird kein zusätzliches Render-Ziel verwendet.
- Rendering in eine Textur und Erstellen eines texturierten Knotens im Szenengraphen. Dies erfordert einen zusätzlichen Render-Durchlauf und ein zusätzliches Renderziel.
- Ausführung von Draw-Calls inline mit dem eigenen Rendering des Szenengraphen durch Instanziierung einer Unterklasse von ` QSGRenderNode ` im Szenengraphen. Dies ähnelt dem ersten Ansatz, jedoch werden die benutzerdefinierten Draw-Calls effektiv in den Befehlsstrom des Szenengraphen eingefügt.
Underlay-/Overlay-Modus
Durch die Anbindung an die Signale „ QQuickWindow::beforeRendering()“ und „ QQuickWindow::afterRendering()“ können Anwendungen „ QRhi “- oder native 3D-API-Aufrufe direkt in denselben Kontext ausführen, in den der Szenengraph rendert. Mit APIs wie Vulkan oder Metal können Anwendungen native Objekte, wie beispielsweise den Befehlspuffer des Szenengraphen, über „ QSGRendererInterface “ abfragen und Befehle nach eigenem Ermessen darin speichern. Wie die Signalnamen andeuten, kann der Benutzer Inhalte dann entweder unter einer „ Qt Quick “-Szene oder darüber rendern. Der Vorteil einer solchen Integration besteht darin, dass für das Rendern keine zusätzlichen Renderziele benötigt werden und ein möglicherweise aufwendiger Texturierungsschritt entfällt. Der Nachteil ist, dass das benutzerdefinierte Rendern nur entweder zu Beginn oder am Ende des eigenen Renderings von „ Qt Quick “ ausgelöst werden kann. Die Verwendung von ` QSGRenderNode ` anstelle der Signale ` QQuickWindow ` kann diese Einschränkung etwas aufheben, doch in beiden Fällen ist bei 3D-Inhalten und der Nutzung des Tiefenpuffers Vorsicht geboten, da das Verlassen auf Tiefenprüfungen und das Rendern mit aktiviertem Tiefenschreiben leicht zu Situationen führen kann, in denen die Nutzung des Tiefenpuffers durch benutzerdefinierte Inhalte und durch ` Qt Quick `-Inhalte miteinander in Konflikt gerät.
Ab Qt 6.6 gelten die „ QRhi “-APIs als halböffentlich, d. h., sie werden den Anwendungen zur Verfügung gestellt und dokumentiert, wenn auch mit einer eingeschränkten Kompatibilitätsgarantie. Dies ermöglicht die Erstellung von portierbarem, plattformübergreifendem 2D/3D-Rendering-Code durch die Verwendung derselben Grafik- und Shader-Abstraktionen, die auch der Szenengraph selbst nutzt.
Das Beispiel „Scene Graph – RHI unter QML“ veranschaulicht, wie der Underlay/Overlay-Ansatz mithilfe von „ QRhi “ implementiert werden kann.
Das Beispiel „Scene Graph – OpenGL unter QML “ veranschaulicht, wie diese Signale unter Verwendung von OpenGL genutzt werden können.
Das Beispiel „Scene Graph – Direct3D 11 unter QML “ veranschaulicht die Verwendung dieser Signale mit Direct3D.
Das Beispiel „Scene Graph – Metal unter QML“ veranschaulicht, wie diese Signale unter Verwendung von Metal genutzt werden können.
Das Beispiel „Scene Graph – Vulkan unter QML “ veranschaulicht die Verwendung dieser Signale unter Verwendung von Vulkan.
Ab Qt 6.0 muss die direkte Nutzung der zugrunde liegenden Grafik-API durch einen Aufruf von „ QQuickWindow::beginExternalCommands()“ und „ QQuickWindow::endExternalCommands()“ umschlossen werden. Dieses Konzept ist möglicherweise aus „ QPainter::beginNativePainting()“ bekannt und dient einem ähnlichen Zweck: Es ermöglicht dem „ Qt Quick “-Szenengraphen zu erkennen, dass alle zwischengespeicherten Zustände und Annahmen über den Zustand innerhalb des aktuell aufgezeichneten Renderdurchgangs – sofern vorhanden – nun ungültig sind, da der Anwendungscode diese möglicherweise durch direkte Arbeit mit der zugrunde liegenden Grafik-API verändert hat. Dies gilt nicht und ist nicht erforderlich, wenn „ QRhi “ verwendet wird.
Beim Kombinieren von benutzerdefiniertem OpenGL-Rendering mit dem Szenenbaum ist es wichtig, dass die Anwendung den OpenGL-Kontext nicht in einem Zustand hinterlässt, in dem Puffer gebunden, Attribute aktiviert oder spezielle Werte im Z-Puffer, Stencil-Puffer oder Ähnlichem vorhanden sind. Dies kann zu unvorhersehbarem Verhalten führen.
Der benutzerdefinierte Rendering-Code muss thread-bewusst sein, d. h., er darf nicht davon ausgehen, dass er auf dem GUI-(Haupt-)Thread der Anwendung ausgeführt wird. Beim Anbinden an die Signale „ QQuickWindow “ sollte die Anwendung „ Qt::DirectConnection “ verwenden und berücksichtigen, dass die angebundenen Slots auf dem dedizierten Render-Thread des Szenengraphen aufgerufen werden, sofern ein solcher vorhanden ist.
Der texturbasierte Ansatz
Die texturbasierte Alternative ist der flexibelste Ansatz, wenn die Anwendung ein „abgeflachtes“ 2D-Bild eines benutzerdefinierten 3D-Renderings innerhalb der „ Qt Quick “-Szene benötigt. Dies ermöglicht auch die Verwendung eines dedizierten Tiefen-/Stencil-Puffers, der unabhängig von den vom Hauptrenderdurchlauf verwendeten Puffern ist.
Bei der Verwendung von OpenGL kann die veraltete Komfortklasse „ QQuickFramebufferObject “ genutzt werden, um dies zu erreichen. Auch auf „ QRhi “ basierende benutzerdefinierte Renderer und andere Grafik-APIs als OpenGL können diesem Ansatz folgen, auch wenn „ QQuickFramebufferObject “ diese derzeit nicht unterstützt. Das direkte Erstellen und Rendern einer Textur mit der zugrunde liegenden API, gefolgt vom Einbinden und Verwenden dieser Ressource in einer „ Qt Quick “-Szene in einem benutzerdefinierten „ QQuickItem “, wird in den folgenden Beispielen veranschaulicht:
Szenenbaum – Beispiel fürein RHI-Textur-Element.
Szenenbaum – Beispiel fürden Import einer Vulkan-Textur.
Szenengraph – Beispiel fürden Import einer Metal-Textur.
Der Inline-Ansatz
Mithilfe von „ QSGRenderNode “ werden die benutzerdefinierten Draw-Calls nicht am Anfang oder am Ende der Aufzeichnung des Render-Passes des Szenengraphen eingefügt, sondern während des Rendering-Prozesses des Szenengraphen. Dies wird durch die Erstellung eines benutzerdefinierten „ QQuickItem “ erreicht, der auf einer Instanz von „ QSGRenderNode “ basiert – einem Scene-Graph-Knoten, der speziell dafür vorgesehen ist, Grafikbefehle entweder über „ QRhi “ oder eine native 3D-API wie OpenGL, Vulkan, Metal oder Direct 3D auszuführen.
Das Beispiel „Scene Graph – Custom QSGRenderNode“ veranschaulicht diesen Ansatz.
Benutzerdefinierte Elemente mit QPainter
Der „ QQuickItem “ stellt eine Unterklasse bereit, „ QQuickPaintedItem “, die es dem Benutzer ermöglicht, Inhalte mithilfe von „ QPainter “ zu rendern.
Warnung: Bei der Verwendung von „ QQuickPaintedItem “ wird eine indirekte 2D-Oberfläche zum Rendern der Inhalte genutzt, entweder mittels Software-Rasterisierung oder mithilfe eines OpenGL-Framebuffer-Objekts (FBO), sodass das Rendern in zwei Schritten erfolgt. Zunächst wird die Oberfläche gerasterisiert, anschließend wird sie gezeichnet. Die direkte Verwendung der Szenengraph-API ist stets deutlich schneller.
Protokollierungsunterstützung
Der Szenenbaum unterstützt eine Reihe von Protokollierungskategorien. Diese können sowohl bei der Suche nach Leistungsproblemen als auch nach Fehlern hilfreich sein und sind zudem für Qt-Mitwirkende von Nutzen.
qt.scenegraph.time.texture- Protokolliert die für das Hochladen von Texturen aufgewendete Zeitqt.scenegraph.time.compilation- Protokolliert die für die Shader-Kompilierung aufgewendete Zeitqt.scenegraph.time.renderer- Protokolliert die Zeit, die in den verschiedenen Schritten des Renderers aufgewendet wirdqt.scenegraph.time.renderloop- Protokolliert die Zeit, die in den verschiedenen Schritten der Render-Schleife aufgewendet wird. Bei der „threaded“-Render-Schleife gibt dies einen Einblick in die Zeit, die zwischen den verschiedenen Schritten der Frame-Vorbereitung sowohl im GUI- als auch im Render-Thread vergeht. Es kann daher auch ein nützliches Tool zur Fehlerbehebung sein, um beispielsweise zu überprüfen, wie sich vsync-basierte Drosselung und andere Low-Level-Qt-Enabler wie „ QWindow::requestUpdate()“ auf die Rendering- und Darstellungs-Pipeline auswirken.qt.scenegraph.time.glyph- Protokolliert die Zeit, die für die Vorbereitung von Distance-Field-Glyphen aufgewendet wirdqt.scenegraph.general- protokolliert allgemeine Informationen zu verschiedenen Teilen des Szenengraphen und des Grafikstacksqt.scenegraph.renderloop- Erstellt ein detailliertes Protokoll der verschiedenen Phasen des Renderings. Dieser Protokollmodus ist in erster Linie für Entwickler nützlich, die an Qt arbeiten.
Die veraltete Umgebungsvariable „ QSG_INFO “ ist ebenfalls verfügbar. Durch Setzen auf einen Wert ungleich Null wird die Kategorie „ qt.scenegraph.general “ aktiviert.
Hinweis: Bei Grafikproblemen oder wenn Sie sich nicht sicher sind, welche Render-Schleife oder Grafik-API verwendet wird, starten Sie die Anwendung immer mit mindestens den aktivierten Optionen „ qt.scenegraph.general “ und „ qt.rhi.* “ oder mit der Einstellung „ QSG_INFO=1 “. Dadurch werden während der Initialisierung einige wichtige Informationen in die Debug-Ausgabe geschrieben.
Szenengraph-Backend
Zusätzlich zur öffentlichen API verfügt der Szenenbaum über eine Anpassungsschicht, die die Implementierung für hardwarespezifische Anpassungen öffnet. Dabei handelt es sich um eine undokumentierte, interne und private Plugin-API, die es Hardware-Anpassungsteams ermöglicht, das Potenzial ihrer Hardware optimal auszuschöpfen. Sie umfasst:
- Benutzerdefinierte Texturen; insbesondere die Implementierung von ` QQuickWindow::createTextureFromImage ` und die interne Darstellung der Textur, die von den Typen ` Image ` und ` BorderImage ` verwendet wird.
- Benutzerdefinierter Renderer: Die Anpassungsschicht ermöglicht es dem Plugin, zu bestimmen, wie der Szenengraph durchlaufen und gerendert wird. Dadurch lässt sich der Rendering-Algorithmus für eine bestimmte Hardware optimieren oder es können Erweiterungen genutzt werden, die die Leistung verbessern.
- Benutzerdefinierte Szenengraph-Implementierung vieler der standardmäßigen QML-Typen, einschließlich der Text- und Schriftartendarstellung.
- Benutzerdefinierter Animations-Treiber; ermöglicht es dem Animationssystem, sich in die vertikale Bildwiederholfrequenz der Anzeige auf niedriger Ebene einzubinden, um ein flüssiges Rendering zu erzielen.
- Benutzerdefinierte Render-Schleife; ermöglicht eine bessere Kontrolle darüber, wie QML mit mehreren Fenstern umgeht.
© 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.