Qt Quick Szene-Diagramm
Der Szenendiagramm in Qt Quick
Qt Quick 2 verwendet einen dedizierten Szenengraphen, der dann über eine Grafik-API wie OpenGL ES, OpenGL, Vulkan, Metal oder Direct 3D durchlaufen und gerendert wird. Die Verwendung eines Szenegraphen für Grafiken anstelle der traditionellen imperativen Mal-Systeme (QPainter und ähnliche) bedeutet, dass die zu rendernde Szene zwischen den Frames beibehalten werden kann und der vollständige Satz der zu rendernden Primitive bekannt ist, bevor das Rendern beginnt. Dies ermöglicht eine Reihe von Optimierungen, wie z. B. Batch-Rendering zur Minimierung von Zustandsänderungen und zum Verwerfen verdeckter Primitive.
Ein Beispiel: Eine Benutzeroberfläche enthält eine Liste mit zehn Elementen, wobei jedes Element eine Hintergrundfarbe, ein Symbol und einen Text hat. Bei Verwendung herkömmlicher 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 reorganisieren, dass alle Hintergründe in einem Aufruf gezeichnet werden, dann alle Symbole, dann der gesamte Text, wodurch die Gesamtzahl der Zeichenaufrufe auf nur 3 reduziert wird. Die Stapelung und die Reduzierung der Zustandsänderungen auf diese Weise können die Leistung auf einiger Hardware erheblich verbessern.
Der Szenegraph ist eng mit Qt Quick 2.0 verknüpft und kann nicht eigenständig verwendet werden. Der Szenegraph wird von der Klasse QQuickWindow verwaltet und gerendert, und benutzerdefinierte Elementtypen können ihre grafischen Primitive durch einen Aufruf von QQuickItem::updatePaintNode() in den Szenegraph einfügen.
Der Szenegraph ist eine grafische Darstellung der Item-Szene, eine unabhängige Struktur, die genügend Informationen enthält, um alle Items darzustellen. Sobald er eingerichtet ist, kann er unabhängig vom Zustand der Elemente manipuliert und gerendert werden. Auf vielen Plattformen wird der Szenegraph sogar in einem eigenen Render-Thread gerendert, während der GUI-Thread den Zustand des nächsten Frames vorbereitet.
Hinweis: Viele der auf dieser Seite aufgeführten Informationen beziehen sich auf das eingebaute, standardmäßige Verhalten des Qt Quick Szenegraphen. Bei Verwendung einer alternativen Szenengraphenanpassung, wie z. B. der software
Anpassung, treffen möglicherweise nicht alle Konzepte zu. Weitere Informationen zu den verschiedenen Szenegraph-Anpassungen finden Sie unter Szenegraph-Anpassungen.
Qt Quick Struktur des Szenegraphen
Der Szenegraph besteht aus einer Reihe von vordefinierten Knotentypen, die jeweils einem bestimmten Zweck dienen. Obwohl wir ihn als Szenengraph bezeichnen, ist eine genauere Definition der 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 virtuelle paint()
Funktionen.
Obwohl der Knotenbaum größtenteils intern aus den vorhandenen Qt Quick QML-Typen aufgebaut wird, können Benutzer auch komplette Teilbäume mit eigenem Inhalt hinzufügen, einschließlich Teilbäume, die 3D-Modelle darstellen.
Knotenpunkte
Der wichtigste Knoten für den Benutzer ist QSGGeometryNode. Er wird zur Definition von benutzerdefinierten Grafiken verwendet, indem seine Geometrie und sein Material festgelegt werden. Die Geometrie wird mit QSGGeometry definiert und beschreibt die Form oder das Netz des grafischen Primitivs. Dabei kann es sich um eine Linie, ein Rechteck, ein Polygon, viele unverbundene 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 Unterknoten haben, und Geometrieknoten werden so gerendert, dass sie in der Reihenfolge der Unterknoten erscheinen, wobei die Eltern hinter ihren Kindern stehen.
Hinweis: Dies sagt nichts über die tatsächliche Rendering-Reihenfolge im Renderer aus. Nur die visuelle Ausgabe ist garantiert.
Die verfügbaren Knoten sind:
Implementiert die Clipping-Funktionalität im Szenegraphen | |
Wird für alle gerenderten Inhalte im Szenegraphen verwendet | |
Die Basisklasse für alle Knoten im Szenegraphen | |
Wird verwendet, um die Deckkraft von Knoten zu ändern | |
Implementiert Transformationen im Szenegraphen |
Benutzerdefinierte Knoten werden dem Szenegraphen hinzugefügt, indem die Unterklasse QQuickItem::updatePaintNode() verwendet und das Flag QQuickItem::ItemHasContents gesetzt wird.
Warnung: Es ist von entscheidender Bedeutung, dass native Grafikoperationen (OpenGL, Vulkan, Metal usw.) und die Interaktion mit dem Szenengraphen ausschließlich auf dem Render-Thread stattfinden, hauptsächlich während des updatePaintNode()-Aufrufs. Als Faustregel gilt, dass nur Klassen mit dem Präfix "QSG" innerhalb der Funktion QQuickItem::updatePaintNode() verwendet werden sollten.
Weitere Details finden Sie unter Scene Graph - Custom Geometry.
Vorverarbeitung
Knoten haben eine virtuelle QSGNode::preprocess() Funktion, die aufgerufen wird, bevor der Szenegraph gerendert wird. Knotenunterklassen können das Flag QSGNode::UsePreprocess setzen und die Funktion QSGNode::preprocess() überschreiben, um die endgültige Vorbereitung ihres Knotens durchzuführen. Zum Beispiel kann eine Bézier-Kurve in die richtige Detailstufe für den aktuellen Skalierungsfaktor unterteilt oder ein Abschnitt einer Textur aktualisiert werden.
Knoteneigentum
Die Eigentümerschaft der Knoten wird entweder explizit durch den Ersteller oder durch den Szenengraphen durch Setzen des Flags QSGNode::OwnedByParent festgelegt. Die Zuweisung der Eigentümerschaft an den Szenengraphen ist oft vorzuziehen, da es die Bereinigung vereinfacht, wenn der Szenengraph außerhalb des GUI-Threads lebt.
Materialien
Das Material beschreibt, wie das Innere einer Geometrie in einer QSGGeometryNode gefüllt wird. Es kapselt Grafik-Shader für die Vertex- und Fragment-Phasen der Grafik-Pipeline und bietet reichlich Flexibilität in Bezug auf das, was erreicht werden kann, obwohl die meisten der Qt Quick Elemente selbst nur sehr einfache Materialien verwenden, wie z. B. Volltonfarben und Texturfüllungen.
Für Benutzer, die lediglich eine benutzerdefinierte Schattierung auf einen QML-Elementtyp anwenden möchten, ist es möglich, dies direkt in QML unter Verwendung des Typs ShaderEffect zu tun.
Im Folgenden finden Sie eine vollständige Liste der Materialklassen:
Bequemer Weg zum Rendern von einfarbiger Geometrie im Szenegraph | |
Kapselt den Rendering-Status für ein Shader-Programm | |
Repräsentiert ein Grafik-API-unabhängiges Shader-Programm | |
Wird als eindeutiges Typ-Token in Kombination mit QSGMaterial verwendet | |
Bequemer Weg zum Rendern von texturierter Geometrie im Szenegraphen | |
Bequemer Weg zum Rendern von texturierter Geometrie im Szenegraphen | |
Bequemes Rendering von farbiger Geometrie pro Scheitelpunkt im Szenegraphen |
Komfortable Knoten
Die Szenengraphen-API ist auf niedriger Ebene angesiedelt und konzentriert sich eher auf Leistung als auf Komfort. Das Schreiben von benutzerdefinierten Geometrien und Materialien von Grund auf, selbst die einfachsten, erfordert eine nicht-triviale Menge an Code. Aus diesem Grund enthält die API einige Komfortklassen, um die gängigsten benutzerdefinierten Knoten leicht verfügbar zu machen.
- QSGSimpleRectNode - eine QSGGeometryNode Unterklasse, die eine rechteckige Geometrie mit einem einfarbigen Material definiert.
- QSGSimpleTextureNode - eine QSGGeometryNode Unterklasse, die eine rechteckige Geometrie mit einem Texturmaterial definiert.
Szenengraph und Rendering
Das Rendering des Szenegraphen erfolgt intern in der Klasse QQuickWindow, und es gibt keine öffentliche API für den Zugriff darauf. Es gibt jedoch einige Stellen in der Rendering-Pipeline, an denen der Benutzer Anwendungscode anhängen kann. Dies kann verwendet werden, um benutzerdefinierte Inhalte des Szenegraphen hinzuzufügen oder um beliebige Rendering-Befehle einzufügen, indem die Grafik-API (OpenGL, Vulkan, Metal usw.), die vom Szenegraphen verwendet wird, direkt aufgerufen wird. Die Integrationspunkte werden durch die Rendering-Schleife definiert.
Eine detaillierte Beschreibung der Funktionsweise des Scene Graph Renderers finden Sie unter Qt Quick Scene Graph Default Renderer.
Es stehen zwei Renderloop-Varianten zur Verfügung: basic
threaded
basic
ist single-threaded, während threaded
das Rendering des Szenengraphen auf einem eigenen Thread durchführt. Qt versucht, eine geeignete Schleife auf der Grundlage der Plattform und möglicherweise der verwendeten Grafiktreiber zu wä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 Rendering-Schleife verwendet wird, aktivieren Sie die qt.scenegraph.general
logging category .
Rendering-Schleife mit Gewinde ('threaded')
Bei vielen Konfigurationen erfolgt das Rendering des Szenengraphen über einen eigenen Render-Thread. Dies geschieht, um die Parallelität von Mehrkernprozessoren zu erhöhen und Wartezeiten, wie z. B. das Warten auf einen blockierenden Swap-Buffer-Aufruf, besser zu nutzen. Dies bietet erhebliche Leistungsverbesserungen, schränkt aber auch ein, wo und wann eine Interaktion mit dem Szenegraphen stattfinden kann.
Nachfolgend wird ein einfacher Überblick darüber gegeben, wie ein Bild mit der Threaded-Rendering-Schleife und OpenGL gerendert wird. Die Schritte sind auch bei anderen Grafik-APIs gleich, abgesehen von den OpenGL-Kontextspezifika.
- In der QML-Szene tritt eine Änderung auf, die den Aufruf von
QQuickItem::update()
verursacht. Dies kann zum Beispiel das Ergebnis einer Animation oder einer Benutzereingabe sein. Ein Ereignis wird an den Render-Thread gesendet, um einen neuen Frame zu initiieren. - Der Render-Thread bereitet das Zeichnen eines neuen Frames vor und initiiert einen Block auf dem GUI-Thread.
- Während der Render-Thread das neue Bild vorbereitet, ruft der GUI-Thread QQuickItem::updatePolish() auf, um die Elemente vor dem Rendern abschließend zu überarbeiten.
- Der GUI-Thread ist blockiert.
- Das Signal QQuickWindow::beforeSynchronizing() wird ausgegeben. Anwendungen können direkte Verbindungen (unter Verwendung von Qt::DirectConnection) zu diesem Signal herstellen, um alle erforderlichen Vorbereitungen vor dem Aufruf von QQuickItem::updatePaintNode() durchzuführen.
- Synchronisierung des QML-Zustands mit dem Szenengraphen. Dies geschieht durch den Aufruf der Funktion QQuickItem::updatePaintNode() für alle Elemente, die sich seit dem vorherigen Frame geändert haben. Dies ist das einzige Mal, dass die QML-Elemente und die Knoten im Szenegraph interagieren.
- Der GUI-Thread-Block wird freigegeben.
- Der Szenegraph wird gerendert:
- Das Signal QQuickWindow::beforeRendering() wird ausgegeben. Anwendungen können direkte Verbindungen (mit Qt::DirectConnection) zu diesem Signal herstellen, um benutzerdefinierte Grafik-API-Aufrufe zu verwenden, die dann visuell unter der QML-Szene gestapelt werden.
- Bei Elementen, die QSGNode::UsePreprocess angegeben haben, wird die Funktion QSGNode::preprocess() aufgerufen.
- Der Renderer verarbeitet die Knoten.
- Der Renderer erzeugt Zustände und zeichnet Zeichenaufrufe für die verwendete Grafik-API auf.
- Das Signal QQuickWindow::afterRendering() wird ausgegeben. Anwendungen können direkte Verbindungen (mit Qt::DirectConnection) zu diesem Signal herstellen, um benutzerdefinierte Grafik-API-Aufrufe zu tätigen, die dann visuell über die QML-Szene gestapelt werden.
- Der Rahmen ist nun bereit. Die Puffer werden ausgetauscht (OpenGL), oder ein aktueller Befehl wird aufgezeichnet und die Befehlspuffer werden an eine Grafikwarteschlange übergeben (Vulkan, Metal). QQuickWindow::frameSwapped() wird ausgegeben.
- 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 ohne Mesa llvmpipe, unter macOS mit Metal, auf mobilen Plattformen und unter Embedded Linux mit EGLFS sowie mit Vulkan unabhängig von der Plattform verwendet. All dies kann sich in zukünftigen Versionen ändern. Es ist immer möglich, die Verwendung des Threaded Renderers zu erzwingen, indem man QSG_RENDER_LOOP=threaded
in der Umgebung setzt.
Rendering-Schleife ohne Threads ("basic")
Die Rendering-Schleife ohne Threads wird derzeit standardmäßig unter Windows mit OpenGL verwendet, wenn nicht die Standard opengl32.dll des Systems verwendet wird, unter macOS mit OpenGL, WebAssembly und unter Linux mit einigen Treibern. Für letztere ist dies vor allem eine Vorsichtsmaßnahme, da nicht alle Kombinationen von OpenGL-Treibern und Windowing-Systemen getestet wurden.
Unter macOS und OpenGL wird die Threaded-Rendering-Schleife nicht unterstützt, wenn sie mit XCode 10 (10.14 SDK) oder später erstellt wird, da sie unter macOS 10.14 auf Layer-Backed Views umgestellt wird. Sie können mit Xcode 9 (10.13 SDK) erstellen, um das Layer-Backing zu deaktivieren. In diesem Fall ist die Threaded-Rendering-Schleife verfügbar und wird standardmäßig verwendet. Es gibt keine solche Einschränkung mit Metal.
Die Rendering-Schleife mit Threads wird von WebAssembly nicht unterstützt, da die Webplattform nur begrenzte Unterstützung für die Verwendung von WebGL auf anderen Threads als dem Hauptthread und begrenzte Unterstützung für das Blockieren des Hauptthreads bietet.
Auch wenn Sie die Rendering-Schleife ohne Thread verwenden, sollten Sie Ihren Code so schreiben, als ob Sie den Renderer mit Thread verwenden würden, da der Code sonst nicht portabel ist.
Nachfolgend finden Sie eine vereinfachte Darstellung der Rendering-Sequenz im Renderer ohne Thread.
Animationen steuern
Worauf bezieht sich Advance Animations
in den obigen Diagrammen?
Standardmäßig wird eine Qt Quick Animation (z. B. NumberAnimation) durch den Standard-Animationstreiber gesteuert. Dieser stützt sich auf grundlegende System-Timer, wie QObject::startTimer(). Der Timer läuft normalerweise mit einem Intervall von 16 Millisekunden. Dies wird zwar nie ganz genau sein und hängt auch von der Genauigkeit der Zeitgeber in der zugrunde liegenden Plattform ab, hat aber den Vorteil, dass es unabhängig vom Rendering ist. Es liefert einheitliche Ergebnisse, unabhängig von der Bildwiederholfrequenz des Bildschirms und davon, ob die Synchronisierung mit der vertikalen Synchronisierung des Bildschirms aktiv ist oder nicht. So funktionieren Animationen mit der basic
Rendering-Schleife.
Um genauere Ergebnisse mit weniger Stottern auf dem Bildschirm zu erzielen, kann eine Rendering-Schleife, unabhängig vom Design der Rendering-Schleife (ob mit einem oder mehreren Threads), beschließen, ihren eigenen benutzerdefinierten Animationstreiber zu installieren und die Bedienung von advancing
selbst in die Hand zu nehmen, ohne sich auf Timer zu verlassen.
Das ist es, was die threaded
Rendering-Schleife implementiert. Tatsächlich installiert sie nicht nur einen, sondern zwei Animationstreiber: einen auf dem Gui-Thread (zur Steuerung regulärer Animationen wie NumberAnimation) und einen auf dem Render-Thread (zur Steuerung der Render-Thread-Animationen, d. h. der Animator -Typen wie OpacityAnimator oder XAnimator). Beide werden während der Vorbereitung eines Frames vorangetrieben, d. h. die Animationen sind jetzt mit dem Rendering synchronisiert. Dies ist sinnvoll, da die Darstellung durch den zugrundeliegenden Grafikstapel auf die vertikale Synchronisation des Displays gedrosselt wird.
Daher gibt es im obigen Diagramm für die Rendering-Schleife threaded
einen expliziten Advance animations
Schritt für beide Threads. Für den Render-Thread ist dies trivial: Da der Thread auf vsync gedrosselt wird, führt das Weiterschalten von Animationen (für Animator Typen) in jedem Frame zu genaueren Ergebnissen, als wenn man sich auf einen System-Timer verlassen würde. (wenn der Thread auf das vsync-Timing gedrosselt wird, das bei einer Bildwiederholfrequenz von 60 Hz bei 1000/60
Millisekunden liegt, kann man davon ausgehen, dass es ungefähr so lange gedauert hat, bis der gleiche Vorgang für das vorherige Bild ausgeführt wurde)
Der gleiche Ansatz funktioniert auch für Animationen im GUI-Thread (Haupt-Thread): Aufgrund der wesentlichen Synchronisierung von Daten zwischen dem GUI- und dem Render-Thread wird der GUI-Thread effektiv auf die gleiche Rate wie der Render-Thread gedrosselt, wobei er den Vorteil hat, dass er weniger Arbeit zu erledigen hat und mehr Spielraum für die Anwendungslogik bleibt, da ein Großteil der Rendering-Vorbereitungen nun auf den Render-Thread verlagert wird.
Während in den obigen Beispielen 60 Bilder pro Sekunde verwendet wurden, ist Qt Quick auch für andere Bildwiederholraten vorbereitet: Die Rate wird von QScreen und der Plattform abgefragt. Bei einem 144-Hz-Bildschirm beträgt das Intervall zum Beispiel 6,94 ms. Gleichzeitig ist es genau das, was zu Problemen führen kann, wenn die vsync-basierte Drosselung nicht wie erwartet funktioniert, denn wenn das, was die Rendering-Schleife denkt, dass es passiert, nicht mit der Realität übereinstimmt, kommt es zu einem falschen Animations-Pacing.
Hinweis: Ab Qt 6.5 bietet die Rendering-Schleife die Möglichkeit, sich für einen anderen Animationstreiber zu entscheiden, der ausschließlich auf der verstrichenen Zeit basiert (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 benötigt wird, um auf QTimer zurückzugreifen, wenn mehrere Fenster vorhanden sind, dass keine Heuristik benötigt wird, um festzustellen, ob die vsync-basierte Drosselung fehlt oder defekt ist, dass sie mit jeder Art von zeitlichen Abweichungen bei der vsync-Drosselung kompatibel ist und dass sie nicht an die Bildwiederholrate des primären Bildschirms gebunden ist, wodurch sie möglicherweise besser in Konfigurationen mit mehreren Bildschirmen funktioniert. Außerdem werden Render-Thread-Animationen (die Animator -Typen) auch dann korrekt ausgeführt, wenn die vsync-basierte Drosselung unterbrochen oder deaktiviert ist. Andererseits können Animationen bei diesem Ansatz als weniger flüssig wahrgenommen werden. Mit Blick auf die Kompatibilität wird diese Funktion derzeit als Opt-in-Funktion angeboten.
Zusammenfassend lässt sich sagen, dass die threaded
Rendering-Schleife flüssigere Animationen mit weniger Stottern liefern dürfte, solange die folgenden Bedingungen erfüllt sind:
- Es befindet sich genau ein Fenster (wie in QQuickWindow) auf dem Bildschirm.
- Die VSync-basierte Drosselung funktioniert wie erwartet mit dem Underyling-Grafik- und Display-Stack.
Was ist, wenn kein oder mehr als ein Fenster sichtbar ist?
Wenn es kein renderndes Fenster gibt, z. B. 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 Bildschirmaktualisierungsrate "arbeitet". In diesem Fall schaltet die threaded
Rendering-Schleife automatisch auf einen System-Timer-basierten Ansatz um, um Animationen zu steuern, d.h. sie schaltet vorübergehend auf den Mechanismus um, den die basic
Schleife verwenden würde.
Das Gleiche gilt, wenn mehr als eine QQuickWindow Instanz auf dem Bildschirm zu sehen ist. Das oben vorgestellte Modell für das Vorantreiben von Animationen auf dem Gui-Thread, das durch seine 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). (Einer pro Fenster.) Hier wird auch ein Rückgriff auf den System-Timer-basierten Ansatz notwendig, denn wie lange und wie oft der GUI-Thread blockiert, hängt nun von einer Reihe von Faktoren ab, einschließlich des Inhalts in den Fenstern (werden sie animiert? wie oft werden sie aktualisiert?) und dem Verhalten des Grafik-Stacks (wie genau geht er mit zwei oder mehr Threads um, die mit wait-for-vsync präsentiert werden?). Da wir nicht garantieren können, dass die Darstellungsrate des Fensters (welches Fenster ist das überhaupt?) auf stabile, plattformübergreifende Weise gedrosselt wird, können fortschreitende Animationen nicht auf dem Rendering basieren.
Diese Umstellung der Mechanismen zur Handhabung von Animationen ist 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 Rendering-Schleife threaded
verlässt sich bei der Drosselung auf die Grafik-API-Implementierung und/oder das Fenstersystem, z. B. durch die Anforderung eines Swap-Intervalls von 1 im Falle von OpenGL (GLX, EGL, WGL), den Aufruf von Present() mit einem Intervall von 1 für Direct 3D oder die Verwendung des Präsentationsmodus FIFO
bei Vulkan.
Einige Grafiktreiber erlauben es dem Benutzer, diese Einstellung außer Kraft zu setzen und zu deaktivieren und die Anfrage von Qt zu ignorieren. Ein Beispiel hierfür wäre ein systemweites Kontrollfeld des Grafiktreibers, das es erlaubt, die Einstellungen der Anwendung in Bezug auf vsync zu überschreiben. Es kann auch vorkommen, dass ein Grafikstack nicht in der Lage ist, eine angemessene vsync-basierte Drosselung bereitzustellen, was in einigen virtuellen Maschinen der Fall sein kann (hauptsächlich aufgrund der Verwendung einer auf Software-Rasterung basierenden Implementierung von OpenGL oder Vulkan).
Ohne die Blockierung der Swap/Present-Operation (oder einer anderen Grafikoperation) würde eine solche Rendering-Schleife die Animationen zu schnell vorantreiben. Bei der Rendering-Schleife basic
wäre dies kein Problem, da sie sich immer auf die System-Timer verlässt. Bei threaded
kann das Verhalten je nach Qt-Version variieren:
- Wenn bekannt ist, dass ein System nicht in der Lage ist, eine vsync-basierte Drosselung bereitzustellen, bestand vor Qt 6.4 die einzige Möglichkeit darin, die
basic
Rendering-Schleife zu verwenden, indemQSG_RENDER_LOOP=basic
vor der Ausführung der Anwendung manuell in der Umgebung eingestellt wurde. - Ab Qt 6.4 kann das Setzen der Umgebungsvariablen
QSG_NO_VSYNC
auf einen Wert ungleich Null oder des Fensters QSurfaceFormat::swapInterval() auf0
das Problem ebenfalls lindern: Durch die explizite Aufforderung, das vsync-basierte Blockieren zu deaktivieren, unabhängig davon, ob die Aufforderung in der Praxis irgendeinen Effekt hat, kann die Rendering-Schleifethreaded
erkennen, dass es sinnlos ist, sich auf vsync zu verlassen, um Animationen zu steuern, und sie wird auf die Verwendung von System-Timern zurückgreifen, so wie es bei mehr als einem Fenster der Fall wäre. - Noch besser ist, dass der Scenegraph ab Qt 6.4 auch versucht, mit Hilfe einiger einfacher Heuristiken zu erkennen, dass die Frames "zu schnell" dargestellt werden, und automatisch auf Systemtimer umschaltet, wenn dies als notwendig angesehen wird. Das bedeutet, dass in den meisten Fällen nichts unternommen werden muss und Anwendungen Animationen wie erwartet ausführen, selbst wenn die Standard-Renderschleife die
threaded
ist. Während dies für die Anwendungen transparent ist, ist es für die Fehlersuche und die Entwicklung nützlich zu wissen, dass dies mit einer"Window 0x7ffc8489c3d0 is determined to have broken vsync throttling ..."
Nachricht protokolliert wird, wennQSG_INFO
oderqt.scenegraph.general
aktiviert ist. Diese Methode hat den Nachteil, dass sie nur nach einer kleinen Anzahl von Frames aktiviert wird, da sie zunächst Daten zur Auswertung sammeln muss, was bedeutet, dass die Anwendung beim Öffnen von QQuickWindow möglicherweise noch für eine kurze Zeit übermäßig schnelle Animationen zeigt. Außerdem werden möglicherweise nicht alle möglichen vsync-broken Situationen erfasst.
Denken Sie jedoch daran, dass dies alles beim Rendern von Thread-Animationen (die Animator Typen) nicht hilft. In Abwesenheit von vsync-basiertem Blockieren wird animators standardmäßig falsch vorankommen, schneller als erwartet, selbst wenn die Umgehungslösungen für reguläre animations aktiviert sind. Wenn dies zu einem Problem wird, sollten Sie den alternativen Animationstreiber verwenden, indem Sie QSG_USE_SIMPLE_ANIMATION_DRIVER
einstellen.
Hinweis: Beachten Sie, dass die Logik der Rendering-Schleife und die Ereignisverarbeitung auf dem GUI-(Haupt-)Thread nicht notwendigerweise ungedrosselt ist, selbst wenn das Warten auf vsync deaktiviert ist: Beide Rendering-Schleifen planen Aktualisierungen für Fenster über QWindow::requestUpdate(). Dies wird auf den meisten Plattformen durch einen 5 ms GUI-Thread-Timer unterstützt, um Zeit für die Ereignisverarbeitung zu haben. Auf einigen Plattformen, z. B. macOS, werden plattformspezifische APIs (z. B. CVDisplayLink) verwendet, um über den richtigen Zeitpunkt für die Vorbereitung eines neuen Frames informiert zu werden, der wahrscheinlich in irgendeiner Form an die Vsync-Funktion des Displays gebunden ist. Dies kann bei Benchmarking und ähnlichen Situationen von Bedeutung sein. Für Anwendungen und Tools, die versuchen, Low-Level-Benchmarking durchzuführen, kann es von Vorteil sein, die Umgebungsvariable QT_QPA_UPDATE_IDLE_TIME
auf 0
zu setzen, um die Leerlaufzeit des GUI-Threads zu reduzieren. Für die normale Anwendungsnutzung sollten die Standardeinstellungen in den meisten Fällen ausreichend sein.
Hinweis: Aktivieren Sie im Zweifelsfall die Protokollierungskategorien qt.scenegraph.general
und qt.scenegraph.time.renderloop
für die Fehlersuche, da diese Hinweise darauf geben können, warum das Rendern und die Animationen nicht in der erwarteten Geschwindigkeit ablaufen.
Benutzerdefinierte Kontrolle über das Rendering mit QQuickRenderControl
Bei Verwendung von QQuickRenderControl wird die Verantwortung für die Steuerung der Rendering-Schleife an die Anwendung übertragen. In diesem Fall wird keine eingebaute Rendering-Schleife verwendet. Stattdessen obliegt es der Anwendung, die Schritte "polish", "synchronize" und "rendering" zum geeigneten Zeitpunkt aufzurufen. Es ist möglich, entweder ein threaded oder ein non-threaded Verhalten zu implementieren, das den oben gezeigten ähnelt.
Zusätzlich können Anwendungen ihren eigenen QAnimationDriver in Kombination mit QQuickRenderControl implementieren und installieren. Dies gibt volle Kontrolle über die Steuerung von Qt Quick Animationen, was besonders wichtig für Inhalte sein kann, die nicht auf dem Bildschirm angezeigt werden und keinen Bezug zur Präsentationsrate haben, weil keine Darstellung des Frames stattfindet. Dies ist optional, standardmäßig laufen die Animationen auf der Grundlage des Systemtimers ab.
Erweiterung des Scene Graphs mit QRhi-basiertem und nativem 3D-Rendering
Der Szenengraf bietet drei Methoden zur Integration von Grafikbefehlen, die von Anwendungen bereitgestellt werden:
- Die Ausgabe von QRhi-basierten oder OpenGL-, Vulkan-, Metal- oder Direct3D-Befehlen direkt vor oder nach dem eigenen Rendering des Szenengraphen. Dadurch wird eine Reihe von Zeichenaufrufen in den Haupt-Rendering-Durchgang vorangestellt oder angehängt. Es wird kein zusätzliches Rendering-Ziel verwendet.
- Rendering auf eine Textur und Erstellen eines texturierten Knotens im Szenegraph. Dies erfordert einen zusätzlichen Renderpass und ein zusätzliches Renderziel.
- Ausgabe von Zeichenaufrufen inline mit dem eigenen Rendering des Szenegraphen durch Instanziierung einer QSGRenderNode Unterklasse im Szenegraphen. Dies ähnelt dem ersten Ansatz, aber die benutzerdefinierten Zeichenaufrufe werden effektiv in den Befehlsstrom des Szenengraphen injiziert.
Underlay/Overlay-Modus
Durch die Verbindung mit den Signalen QQuickWindow::beforeRendering() und QQuickWindow::afterRendering() können Anwendungen QRhi oder native 3D-API-Aufrufe direkt im selben Kontext ausführen, in dem der Szenengraf gerendert wird. Mit APIs wie Vulkan oder Metal können Anwendungen native Objekte wie den Befehlspuffer des Szenengraphen über QSGRendererInterface abfragen und Befehle darin aufzeichnen, wie sie es für richtig halten. Wie die Signalnamen andeuten, kann der Benutzer dann Inhalte entweder unter oder über einer Qt Quick Szene rendern. Der Vorteil einer solchen Integration besteht darin, dass keine zusätzlichen Rendering-Ziele für das Rendering benötigt werden und ein möglicherweise teurer Texturierungsschritt entfällt. Der Nachteil ist, dass das benutzerdefinierte Rendering nur entweder am Anfang oder am Ende des eigenen Renderings von Qt Quick ausgeführt werden kann. Die Verwendung von QSGRenderNode anstelle des QQuickWindow Signals kann diese Einschränkung etwas aufheben, aber in beiden Fällen ist Vorsicht geboten, wenn es um 3D-Inhalte und die Verwendung des Tiefenpuffers geht, da die Verwendung von Tiefenprüfungen und das Rendering mit aktivierter Tiefenschreibfunktion leicht zu Situationen führen kann, in denen der benutzerdefinierte Inhalt und die Verwendung des Tiefenpuffers von Qt Quick in Konflikt miteinander geraten.
Ab Qt 6.6 werden die QRhi APIs als halb-öffentlich betrachtet, d.h. sie werden den Anwendungen angeboten und dokumentiert, wenn auch mit einer begrenzten Kompatibilitätsgarantie. Dies ermöglicht die Erstellung von portierbarem, plattformübergreifendem 2D/3D-Rendering-Code, indem dieselben Grafik- und Shader-Abstraktionen verwendet werden, die auch der Scene Graph selbst nutzt.
Das Beispiel Scene Graph - RHI Under QML zeigt, wie der Underlay/Overlay-Ansatz mit QRhi implementiert werden kann.
Das Beispiel Scene Graph - OpenGL Under QML gibt ein Beispiel für die Verwendung dieser Signale unter OpenGL.
Das Beispiel Scene Graph - Direct3D 11 Under QML gibt ein Beispiel für die Verwendung dieser Signale unter Verwendung von Direct3D.
Das Scene Graph - Metal Under QML Beispiel gibt ein Beispiel für die Verwendung dieser Signale mit Metal.
Das Beispiel Scene Graph - Vulkan Under QML gibt ein Beispiel für die Verwendung dieser Signale unter Verwendung von Vulkan.
Ab Qt 6.0 muss die direkte Verwendung der zugrunde liegenden Grafik-API durch einen Aufruf von QQuickWindow::beginExternalCommands() und QQuickWindow::endExternalCommands() eingeschlossen werden. Dieses Konzept ist vielleicht von QPainter::beginNativePainting() bekannt und dient einem ähnlichen Zweck: Es erlaubt dem Qt Quick Scene Graph zu erkennen, dass jeder zwischengespeicherte Zustand und Annahmen über den Zustand innerhalb des aktuell aufgezeichneten Renderpasses, falls es einen gibt, nun ungültig sind, da der Anwendungscode diesen durch die direkte Arbeit mit der zugrunde liegenden Grafik-API verändert haben könnte. Dies ist nicht anwendbar und notwendig, wenn QRhi verwendet wird.
Beim Mischen von benutzerdefiniertem OpenGL-Rendering mit dem Szenengraphen ist es wichtig, dass die Anwendung den OpenGL-Kontext nicht in einem Zustand mit gebundenen Puffern, aktivierten Attributen, speziellen Werten im z-Buffer oder Stencil-Buffer oder ähnlichem belässt. Dies kann zu unvorhersehbarem Verhalten führen.
Der benutzerdefinierte Rendering-Code muss Thread-bewusst sein, d.h. er sollte nicht davon ausgehen, dass er auf dem GUI-Thread (Haupt-Thread) der Anwendung ausgeführt wird. Bei der Verbindung mit den Signalen von QQuickWindow sollte die Anwendung Qt::DirectConnection verwenden und verstehen, dass die verbundenen Slots auf dem dedizierten Render-Thread des Szenegraphen 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 Szene Qt Quick benötigt. Dies ermöglicht auch die Verwendung eines speziellen Tiefen-/Schablonenpuffers, der unabhängig von den Puffern ist, die vom Haupt-Rendering-Pass verwendet werden.
Bei Verwendung von OpenGL kann die Legacy-Convenience-Klasse QQuickFramebufferObject verwendet werden, um dies zu erreichen. QRhi-basierte benutzerdefinierte Renderer und andere Grafik-APIs als OpenGL können diesen Ansatz ebenfalls verfolgen, auch wenn QQuickFramebufferObject sie derzeit nicht unterstützt. Die folgenden Beispiele zeigen, wie eine Textur direkt mit der zugrundeliegenden API erstellt und gerendert wird, gefolgt von der Umhüllung und Verwendung dieser Ressource in einer Qt Quick -Szene in einem benutzerdefinierten QQuickItem:
Scene Graph - RHI Texture Item Beispiel.
Szenengraph - Beispiel fürVulkan-Texturimport.
Szenengraph - Beispiel fürden Import von Metalltexturen.
Der Inline-Ansatz
Mit QSGRenderNode werden die benutzerdefinierten Zeichenaufrufe nicht am Anfang oder am Ende der Aufzeichnung des Rendering-Durchgangs des Szenegraphen injiziert, sondern während des Rendering-Prozesses des Szenegraphen. Dies wird durch die Erstellung eines benutzerdefinierten QQuickItem erreicht, das auf einer Instanz von QSGRenderNode basiert, einem Szenegraph-Knoten, der speziell für die Ausgabe von Grafikbefehlen entweder über QRhi oder eine native 3D-API wie OpenGL, Vulkan, Metal oder Direct 3D existiert.
Das Beispiel Scene Graph - Custom QSGRenderNode demonstriert diesen Ansatz.
Benutzerdefinierte Elemente mit QPainter
QQuickItem bietet eine Unterklasse, QQuickPaintedItem, die es den Benutzern ermöglicht, Inhalte mit QPainter zu rendern.
Achtung! Bei der Verwendung von QQuickPaintedItem wird eine indirekte 2D-Oberfläche verwendet, um den Inhalt zu rendern, entweder unter Verwendung von Software-Rasterung oder unter Verwendung eines OpenGL-Framebuffer-Objekts (FBO), so dass das Rendering ein zweistufiger Vorgang ist. Zuerst wird die Oberfläche gerastert, dann wird sie gezeichnet. Die direkte Verwendung der Szenengraphen-API ist immer deutlich schneller.
Logging-Unterstützung
Der Szenegraph unterstützt eine Reihe von Protokollierungskategorien. Diese können beim Aufspüren von Performance-Problemen und Bugs nützlich sein und sind auch für Qt-Mitarbeiter hilfreich.
qt.scenegraph.time.texture
- protokolliert die Zeit, die mit dem Hochladen von Texturen verbracht wurdeqt.scenegraph.time.compilation
- protokolliert die Zeit, die für die Shader-Kompilierung aufgewendet wirdqt.scenegraph.time.renderer
- protokolliert die Zeit, die in den verschiedenen Schritten des Renderers verbracht wirdqt.scenegraph.time.renderloop
- protokolliert die Zeit, die in den verschiedenen Schritten der Renderschleife verbracht wird. Mit derthreaded
Renderschleife gibt dies einen Einblick in die Zeit, die zwischen den verschiedenen Frame-Vorbereitungsschritten sowohl auf der GUI als auch im Render-Thread vergeht. Es kann daher auch ein nützliches Werkzeug zur Fehlersuche sein, zum Beispiel um festzustellen, wie vsync-basiertes Throttling und andere Low-Level-Qt-Enabler wie QWindow::requestUpdate() die Rendering- und Präsentationspipeline beeinflussen.qt.scenegraph.time.glyph
- protokolliert die Zeit, die für die Vorbereitung von Abstandsfeld-Glyphen aufgewendet wurdeqt.scenegraph.general
- protokolliert allgemeine Informationen über verschiedene Teile des Szenegraphen und des Grafikstapelsqt.scenegraph.renderloop
- erstellt ein detailliertes Protokoll über die verschiedenen Phasen des Rendering-Prozesses. Dieser Protokollmodus ist vor allem für Entwickler nützlich, die mit Qt arbeiten.
Die Legacy-Umgebungsvariable QSG_INFO
ist ebenfalls verfügbar. Wenn sie auf einen Wert ungleich Null gesetzt wird, wird die Kategorie qt.scenegraph.general
aktiviert.
Hinweis: Bei Grafikproblemen oder wenn Sie sich nicht sicher sind, welche Rendering-Schleife oder Grafik-API verwendet wird, starten Sie die Anwendung immer mit mindestens qt.scenegraph.general
und qt.rhi.*
aktiviert oder QSG_INFO=1
gesetzt. Dadurch werden während der Initialisierung einige wichtige Informationen auf der Debug-Ausgabe ausgegeben.
Scene Graph Backend
Zusätzlich zur öffentlichen API verfügt der Szenegraph über eine Anpassungsschicht, die der Implementierung die Möglichkeit gibt, hardwarespezifische Anpassungen vorzunehmen. Dabei handelt es sich um eine undokumentierte, interne und private Plugin-API, die es Hardware-Anpassungsteams ermöglicht, das Beste aus ihrer Hardware herauszuholen. Sie beinhaltet:
- 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 lässt das Plugin entscheiden, wie der Szenegraph durchlaufen und gerendert wird, so dass es möglich ist, den Rendering-Algorithmus für eine bestimmte Hardware zu optimieren oder Erweiterungen zu verwenden, die die Leistung verbessern.
- Benutzerdefinierte Szenengraphen-Implementierung vieler der Standard-QML-Typen, einschließlich der Wiedergabe von Text und Schrift.
- Benutzerdefinierter Animationstreiber; ermöglicht es dem Animationssystem, sich in die vertikale Aktualisierung der Low-Level-Anzeige einzuklinken, um ein flüssiges Rendering zu erhalten.
- Benutzerdefinierte Renderschleife; ermöglicht eine bessere Kontrolle darüber, wie QML mit mehreren Fenstern umgeht.
© 2025 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.