Qt Quick Standard-Szenengraphen-Renderer

In diesem Dokument wird erklärt, wie der Standard-Szenengraph-Renderer intern funktioniert, so dass man Code schreiben kann, der ihn in optimaler Weise verwendet, sowohl in Bezug auf die Leistung als auch auf die Funktionen.

Man muss die Interna des Renderers nicht verstehen, um eine gute Leistung zu erzielen. Es kann jedoch bei der Integration mit dem Szenengraph hilfreich sein oder um herauszufinden, warum es nicht möglich ist, die maximale Leistung aus dem Grafikchip herauszuholen.

Hinweis: Auch in dem Fall, in dem jedes Bild einzigartig ist und alles von Grund auf neu hochgeladen wird, wird der Standard-Renderer gut funktionieren.

Die Qt Quick Elemente in einer QML-Szene füllen einen Baum von QSGNode Instanzen. Einmal erstellt, ist dieser Baum eine vollständige Beschreibung, wie ein bestimmtes Bild gerendert werden soll. Er enthält keinerlei Rückverweise auf die Qt Quick Elemente und wird auf den meisten Plattformen in einem separaten Thread verarbeitet und gerendert. Der Renderer ist ein eigenständiger Teil des Szenengraphen, der den QSGNode Baum durchläuft und die in QSGGeometryNode definierte Geometrie und den in QSGMaterial definierten Shader-Status verwendet, um den Grafikstatus zu aktualisieren und Zeichenaufrufe zu generieren.

Bei Bedarf kann der Renderer durch die interne Szenengraphen-Backend-API vollständig ersetzt werden. Dies ist vor allem für Plattformanbieter interessant, die die Vorteile nicht standardisierter Hardwarefunktionen nutzen möchten. Für die Mehrzahl der Anwendungsfälle ist der Standard-Renderer ausreichend.

Der Standard-Renderer konzentriert sich auf zwei primäre Strategien zur Optimierung des Renderings: Batching von Zeichenaufrufen und Beibehaltung der Geometrie auf der GPU.

Stapelverarbeitung

Während eine herkömmliche 2D-API wie QPainter, Cairo oder Context2D geschrieben wird, um Tausende von einzelnen Zeichenaufrufen pro Frame zu verarbeiten, erzielen OpenGL und andere hardwarebeschleunigte APIs die beste Leistung, wenn die Anzahl der Zeichenaufrufe sehr gering ist und die Zustandsänderungen auf ein Minimum beschränkt werden.

Hinweis: Während OpenGL in den folgenden Abschnitten als Beispiel verwendet wird, gelten die gleichen Konzepte auch für andere Grafik-APIs.

Betrachten Sie den folgenden Anwendungsfall:

Die einfachste Art, diese Liste zu zeichnen, ist eine zellenweise Darstellung. Zunächst wird der Hintergrund gezeichnet. Dies ist ein Rechteck mit einer bestimmten Farbe. In OpenGL-Begriffen bedeutet dies, dass man ein Shader-Programm auswählt, das einfarbige Füllungen vornimmt, die Füllfarbe einstellt, die Transformationsmatrix mit den x- und y-Offsets festlegt und dann zum Beispiel glDrawArrays verwendet, um zwei Dreiecke zu zeichnen, die das Rechteck bilden. Als nächstes wird das Symbol gezeichnet. In OpenGL-Begriffen bedeutet dies, dass ein Shader-Programm zum Zeichnen von Texturen ausgewählt wird, die zu verwendende aktive Textur ausgewählt wird, die Transformationsmatrix eingestellt wird, Alpha-Blending aktiviert wird und dann z. B. glDrawArrays verwendet wird, um die beiden Dreiecke zu zeichnen, die das begrenzende Rechteck des Symbols bilden. Der Text und die Trennlinie zwischen den Zellen folgen einem ähnlichen Muster. Und dieser Prozess wird für jede Zelle in der Liste wiederholt, so dass bei einer längeren Liste der durch OpenGL-Zustandsänderungen und Zeichenaufrufe entstehende Overhead den Vorteil, den die Verwendung einer hardwarebeschleunigten API bieten könnte, vollständig aufwiegt.

Wenn jedes Primitiv groß ist, ist dieser Overhead vernachlässigbar, aber im Fall einer typischen Benutzeroberfläche gibt es viele kleine Elemente, die sich zu einem erheblichen Overhead summieren.

Der Standard-Szenengraph-Renderer arbeitet innerhalb dieser Grenzen und versucht, einzelne Primitive zu Stapeln zusammenzufassen, wobei das gleiche visuelle Ergebnis erhalten bleibt. Das Ergebnis sind weniger OpenGL-Zustandsänderungen und eine minimale Anzahl von Zeichenaufrufen, was zu einer optimalen Leistung führt.

Undurchsichtige Primitive

Der Renderer trennt zwischen undurchsichtigen Primitiven und Primitiven, die Alpha-Blending erfordern. Durch die Verwendung des Z-Puffers von OpenGL und die Zuweisung einer eindeutigen Z-Position für jedes Primitiv kann der Renderer opake Primitive frei anordnen, ohne Rücksicht auf ihre Position auf dem Bildschirm und die anderen Elemente, mit denen sie sich überschneiden. Anhand des Materialzustands jedes Primitivs erstellt der Renderer undurchsichtige Stapel. Aus dem Qt Quick core item set umfasst dies Rechteck-Elemente mit undurchsichtigen Farben und vollständig undurchsichtige Bilder, wie JPEGs oder BMPs.

Ein weiterer Vorteil der Verwendung von opaken Primitiven ist, dass für opake Primitive nicht GL_BLEND aktiviert werden muss, was insbesondere auf mobilen und eingebetteten GPUs recht kostspielig sein kann.

Opake Primitive werden von vorne nach hinten gerendert, wenn glDepthMask und GL_DEPTH_TEST aktiviert sind. Auf GPUs, die intern Early-z-Prüfungen durchführen, bedeutet dies, dass der Fragment-Shader nicht für Pixel oder Pixelblöcke ausgeführt werden muss, die verdeckt sind. Beachten Sie, dass der Renderer diese Knoten immer noch berücksichtigen muss und der Vertex-Shader immer noch für jeden Vertex in diesen Primitiven ausgeführt wird. Wenn die Anwendung also weiß, dass etwas vollständig verdeckt ist, ist es am besten, es explizit mit Item::visible oder Item::opacity zu verbergen.

Hinweis: Die Item::z wird verwendet, um die Stapelreihenfolge eines Objekts relativ zu seinen Geschwistern zu steuern. Es hat keinen direkten Bezug zum Renderer und OpenGLs Z-Buffer.

Alpha-überblendete Primitive

Sobald opake Primitive gezeichnet wurden, deaktiviert der Renderer glDepthMask, aktiviert GL_BLEND und rendert alle Alpha Blended Primitives in einer Back-to-Front Weise.

Das Batching von Alpha-Blending-Primitiven erfordert etwas mehr Aufwand im Renderer, da sich überlappende Elemente in der richtigen Reihenfolge gerendert werden müssen, damit das Alpha-Blending korrekt aussieht. Sich allein auf den Z-Puffer zu verlassen, reicht nicht aus. Der Renderer führt einen Durchlauf über alle Alpha-Blending-Primitive durch und betrachtet deren Begrenzungsrechteck zusätzlich zu ihrem Materialstatus, um herauszufinden, welche Elemente zusammengefügt werden können und welche nicht.

Im Fall ganz links können die blauen Hintergründe in einem Aufruf gezeichnet werden und die beiden Textelemente in einem weiteren Aufruf, da die Texte nur den Hintergrund überlappen, vor dem sie gestapelt sind. Im rechten Fall überlappt der Hintergrund von "Element 4" den Text von "Element 3", so dass in diesem Fall jeder Hintergrund und jeder Text mit separaten Aufrufen gezeichnet werden muss.

Z-mäßig sind die Alpha-Primitive mit den opaken Knoten verschachtelt und können early-z auslösen, wenn sie verfügbar sind, aber auch hier ist es immer schneller, Item::visible auf false zu setzen.

Mischen mit 3D-Primitiven

Der Szenegraph kann Pseudo-3D- und echte 3D-Primitive unterstützen. Zum Beispiel kann man einen "Page Curl"-Effekt mit ShaderEffect oder einen Bumpmapped Torus mit QSGGeometry und einem benutzerdefinierten Material implementieren. Dabei ist zu beachten, dass der Standard-Renderer bereits den Tiefenpuffer nutzt.

Der Renderer modifiziert den Vertex-Shader, der von QSGMaterialShader::vertexShader() zurückgegeben wird, und komprimiert die z-Werte des Vertex, nachdem die Modellansicht und die Projektionsmatrizen angewandt wurden, und fügt dann eine kleine Translation auf den z-Wert hinzu, um ihn in die richtige z-Position zu bringen.

Bei der Komprimierung wird davon ausgegangen, dass die z-Werte im Bereich von 0 bis 1 liegen.

Textur-Atlas

Die aktive Textur ist ein eindeutiger OpenGL-Zustand, was bedeutet, dass mehrere Primitive, die verschiedene OpenGL-Texturen verwenden, nicht zusammengefügt werden können. Aus diesem Grund ermöglicht der Qt Quick Szenegraph die Zuweisung mehrerer QSGTexture Instanzen als kleinere Unterregionen einer größeren Textur: ein Texturatlas.

Der größte Vorteil von Texturatlanten ist, dass mehrere QSGTexture Instanzen nun auf die gleiche OpenGL-Texturinstanz verweisen. Dies macht es möglich, auch texturierte Zeichenaufrufe zu bündeln, wie z.B. Image-Elemente, BorderImage -Elemente, ShaderEffect -Elemente und auch C++-Typen wie QSGSimpleTextureNode und benutzerdefinierte QSGGeometryNodes, die Texturen verwenden.

Hinweis: Große Texturen werden nicht in den Texturatlas aufgenommen.

Atlasbasierte Texturen werden erstellt, indem QQuickWindow::TextureCanUseAtlas an QQuickWindow::createTextureFromImage() übergeben wird.

Hinweis: Atlasbasierte Texturen haben keine Texturkoordinaten, die von 0 bis 1 reichen. Verwenden Sie QSGTexture::normalizedTextureSubRect(), um die Koordinaten der Atlas-Textur zu erhalten.

Der Szenengraf verwendet Heuristiken, um herauszufinden, wie groß der Atlas sein sollte und was die Größenschwelle für die Aufnahme in den Atlas ist. Wenn andere Werte benötigt werden, ist es möglich, diese mit den Umgebungsvariablen QSG_ATLAS_WIDTH=[width], QSG_ATLAS_HEIGHT=[height] und QSG_ATLAS_SIZE_LIMIT=[size] zu überschreiben. Das Ändern dieser Werte ist vor allem für Plattformanbieter interessant.

Batch-Wurzeln

Neben der Zusammenführung kompatibler Primitive zu Stapeln versucht der Standard-Renderer auch, die Datenmenge zu minimieren, die für jedes Bild an die GPU gesendet werden muss. Der Standard-Renderer identifiziert Teilbäume, die zusammengehören, und versucht, diese in separaten Stapeln zusammenzufassen. Sobald die Stapel identifiziert sind, werden sie zusammengeführt, hochgeladen und im GPU-Speicher unter Verwendung von Vertex Buffer Objects gespeichert.

Transformationsknoten

Jedes Qt Quick Element fügt eine QSGTransformNode in den Szenengraphenbaum ein, um seine x-, y-, Skalierung oder Drehung zu verwalten. Untergeordnete Elemente werden unter diesem Transformationsknoten eingefügt. Der Standard-Renderer verfolgt den Zustand von Transformationsknoten zwischen Frames und schaut sich Teilbäume an, um zu entscheiden, ob ein Transformationsknoten ein guter Kandidat ist, um eine Wurzel für einen Satz von Batches zu werden. Ein Transformationsknoten, der sich zwischen Frames ändert und einen ziemlich komplexen Teilbaum hat, kann eine Stapelwurzel werden.

QSGGeometryNodes im Teilbaum einer Stapelwurzel werden relativ zur Wurzel auf der CPU vorverformt. Sie werden dann hochgeladen und auf der GPU gespeichert. Wenn sich die Transformation ändert, muss der Renderer nur die Matrix der Wurzel und nicht jedes einzelne Element aktualisieren, was das Scrollen von Listen und Gittern sehr schnell macht. Solange keine Knoten hinzugefügt oder entfernt werden, ist das Rendern der Liste bei aufeinanderfolgenden Frames praktisch kostenlos. Wenn neue Inhalte in den Teilbaum gelangen, wird der Stapel, der sie erhält, neu aufgebaut, aber das ist immer noch relativ schnell. Beim Schwenken durch ein Gitter oder eine Liste gibt es in der Regel mehrere unveränderte Frames für jeden Frame mit hinzugefügten oder entfernten Knoten.

Ein weiterer Vorteil der Identifizierung von Transformationsknoten als Stapelwurzeln besteht darin, dass der Renderer die Teile des Baums beibehalten kann, die sich nicht geändert haben. Nehmen wir an, eine Benutzeroberfläche besteht aus einer Liste und einer Schaltflächenzeile. Wenn die Liste gescrollt wird und Delegierte hinzugefügt und entfernt werden, bleibt der Rest der Benutzeroberfläche, die Schaltflächenzeile, unverändert und kann mit der bereits auf der GPU gespeicherten Geometrie gezeichnet werden.

Der Knoten- und Vertex-Schwellenwert für einen Transformationsknoten, um eine Stapelwurzel zu werden, kann mit den Umgebungsvariablen QSG_RENDERER_BATCH_NODE_THRESHOLD=[count] und QSG_RENDERER_BATCH_VERTEX_THRESHOLD=[count] außer Kraft gesetzt werden. Das Überschreiben dieser Flags ist vor allem für Plattformanbieter nützlich.

Hinweis: Unterhalb einer Batch-Root wird ein Batch für jeden eindeutigen Satz von Materialstatus und Geometrietyp erstellt.

Clipping

Wenn Item::clip auf true gesetzt wird, wird ein QSGClipNode mit einem Rechteck in seiner Geometrie erstellt. Der Standard-Renderer wendet diese Beschneidung an, indem er das Scissoring in OpenGL verwendet. Wenn das Element um einen Winkel gedreht wird, der nicht 90 Grad beträgt, wird der OpenGL-Schablonenpuffer verwendet. Qt Quick Item unterstützt nur das Setzen eines Rechtecks als Clip durch QML, aber die Szenengraphen-API und der Standard-Renderer können jede beliebige Form zum Clipping verwenden.

Wenn ein Clip auf einen Teilbaum angewendet wird, muss dieser Teilbaum mit einem eindeutigen OpenGL-Status gerendert werden. Das bedeutet, dass, wenn Item::clip wahr ist, das Stapeln dieses Elements auf seine Kinder beschränkt ist. Wenn es viele Kinder gibt, wie ListView oder GridView, oder komplexe Kinder, wie TextArea, ist dies in Ordnung. Bei kleineren Elementen ist jedoch Vorsicht geboten, da der Clip das Stapeln verhindert. Dazu gehören Schaltflächenbeschriftungen, Textfelder oder Listendelegierte und Tabellenzellen. Das Beschneiden eines Flickable (oder einer Elementansicht) kann oft vermieden werden, indem man die Benutzeroberfläche so anordnet, dass undurchsichtige Elemente Bereiche um das Flickable herum abdecken, und sich ansonsten auf die Fensterränder verlässt, um alles andere zu beschneiden.

Wenn Sie Item::clip auf true setzen, wird auch das Flag QQuickItem::ItemIsViewport gesetzt; untergeordnete Elemente mit dem Flag QQuickItem::ItemObservesViewport können das Ansichtsfenster für einen groben Schritt vor dem Ausschneiden verwenden: z.B. lässt Text Textzeilen aus, die vollständig außerhalb des Ansichtsfensters liegen. Das Weglassen von Knoten des Szenegraphen oder die Begrenzung von vertices ist eine Optimierung, die durch das Setzen von flags in C++ erreicht werden kann, anstatt Item::clip in QML zu setzen.

Bei der Implementierung von QQuickItem::updatePaintNode() in einem benutzerdefinierten Element sollten Sie, wenn es viele Details über einen großen geometrischen Bereich rendern kann, darüber nachdenken, ob es effizient ist, die Grafiken auf das Ansichtsfenster zu beschränken; wenn ja, können Sie das Flag ItemObservesViewport setzen und den aktuell belichteten Bereich aus QQuickItem::clipRect() lesen. Eine Folge davon ist, dass updatePaintNode() häufiger aufgerufen wird (typischerweise einmal pro Frame, wenn sich der Inhalt im Ansichtsfenster bewegt).

Vertex-Puffer

Jeder Stapel verwendet ein Vertex-Buffer-Objekt (VBO), um seine Daten auf der GPU zu speichern. Dieser Scheitelpunktpuffer wird zwischen den Frames beibehalten und aktualisiert, wenn sich der Teil des Szenegraphen, den er darstellt, ändert.

Standardmäßig lädt der Renderer die Daten mit GL_STATIC_DRAW in den VBO hoch. Es ist möglich, eine andere Upload-Strategie zu wählen, indem Sie die Umgebungsvariable QSG_RENDERER_BUFFER_STRATEGY=[strategy] setzen. Gültige Werte sind stream und dynamic. Das Ändern dieses Wertes ist vor allem für Plattformanbieter nützlich.

Antialiasing

Der Szenegraph unterstützt zwei Arten von Antialiasing. Standardmäßig werden Primitive wie Rechtecke und Bilder mit Antialiasing versehen, indem mehr Scheitelpunkte entlang der Kante der Primitive hinzugefügt werden, so dass die Kanten transparent werden. Wir nennen diese Methode Vertex-Antialiasing. Wenn der Benutzer einen OpenGL-Kontext mit Mehrfachabtastung anfordert, indem er mit QQuickWindow::setFormat() eine QSurfaceFormat mit Abtastwerten größer als 0 einstellt, wird der Szenegraph Multisample Based Antialiasing (MSAA) bevorzugen. Die beiden Techniken beeinflussen, wie das Rendering intern abläuft und haben unterschiedliche Einschränkungen.

Es ist auch möglich, die verwendete Antialiasing-Methode außer Kraft zu setzen, indem Sie die Umgebungsvariable QSG_ANTIALIASING_METHOD entweder auf vertex oder msaa setzen.

Vertex-Antialiasing kann Nahtstellen zwischen den Kanten benachbarter Primitive erzeugen, selbst wenn die beiden Kanten mathematisch gleich sind. Beim Multisample-Antialiasing ist dies nicht der Fall.

Vertex-Antialiasing

Vertex-Antialiasing kann mit der Eigenschaft Item::antialiasing für jedes Element aktiviert und deaktiviert werden. Es funktioniert unabhängig davon, was die zugrundeliegende Hardware unterstützt, und erzeugt eine höhere Qualität des Antialiasing, sowohl für normal gerenderte Primitive als auch für Primitive, die in Framebuffer-Objekte eingefangen wurden, zum Beispiel mit dem Typ ShaderEffectSource.

Der Nachteil der Verwendung von Vertex-Antialiasing ist, dass jedes Primitiv mit aktiviertem Antialiasing überblendet werden muss. In Bezug auf die Stapelverarbeitung bedeutet dies, dass der Renderer mehr Arbeit leisten muss, um herauszufinden, ob das Primitiv gestapelt werden kann oder nicht, und aufgrund von Überschneidungen mit anderen Elementen in der Szene kann dies auch zu weniger Stapelverarbeitung führen, was sich auf die Leistung auswirken kann.

Bei einem Bild oder einem abgerundeten Rechteck, das den größten Teil des Bildschirms einnimmt, kann der für das Innere dieser Primitive erforderliche Überblendungsaufwand zu einem erheblichen Leistungsverlust führen, da das gesamte Primitiv überblendet werden muss.

Multisample-Antialiasing

Multisample-Antialiasing ist eine Hardwarefunktion, bei der die Hardware einen Deckungswert pro Pixel im Primitiv berechnet. Manche Hardware kann Multisample mit sehr geringen Kosten durchführen, während andere Hardware sowohl mehr Speicher als auch mehr GPU-Zyklen zum Rendern eines Frames benötigt.

Durch die Verwendung von Multisample-Antialiasing können viele Primitive, wie z. B. abgerundete Rechtecke und Bildelemente, mit Antialiasing versehen werden und sind im Szenegraphen dennoch undurchsichtig. Dies bedeutet, dass der Renderer bei der Erstellung von Stapeln einfacher arbeiten kann und sich auf Early-Z verlassen kann, um ein Überzeichnen zu vermeiden.

Wenn Multisample-Antialiasing verwendet wird, benötigen Inhalte, die in Framebuffer-Objekte gerendert werden, zusätzliche Erweiterungen zur Unterstützung von Multisampling von Framebuffern. In der Regel sind dies GL_EXT_framebuffer_multisample und GL_EXT_framebuffer_blit. Bei den meisten Desktop-Chips sind diese Erweiterungen vorhanden, bei eingebetteten Chips sind sie jedoch weniger verbreitet. Wenn Framebuffer-Multisampling in der Hardware nicht verfügbar ist, werden Inhalte, die in Framebuffer-Objekte gerendert werden, nicht antialiased, auch nicht der Inhalt eines ShaderEffectSource.

Leistung

Wie eingangs erwähnt, ist es nicht erforderlich, die Feinheiten des Renderers zu verstehen, um eine gute Leistung zu erzielen. Der Renderer wurde so geschrieben, dass er für gängige Anwendungsfälle optimiert ist und unter fast allen Umständen recht gut funktioniert.

  • Eine gute Leistung ergibt sich aus einer effektiven Stapelverarbeitung, bei der so wenig wie möglich der Geometrie immer wieder hochgeladen wird. Wenn Sie die Umgebungsvariable QSG_RENDERER_DEBUG=render setzen, gibt der Renderer Statistiken darüber aus, wie gut das Batching funktioniert, wie viele Batches verwendet werden, welche Batches beibehalten werden und welche undurchsichtig sind und welche nicht. Um eine optimale Leistung zu erzielen, sollten Uploads nur dann erfolgen, wenn sie wirklich benötigt werden, die Anzahl der Stapel sollte weniger als 10 betragen und mindestens 3-4 davon sollten undurchsichtig sein.
  • Der Standard-Renderer führt weder CPU-seitiges Viewport Clipping noch Okklusionserkennung durch. Wenn etwas nicht sichtbar sein soll, sollte es auch nicht angezeigt werden. Verwenden Sie Item::visible: false für Elemente, die nicht gezeichnet werden sollen. Der Hauptgrund für den Verzicht auf eine solche Logik ist, dass sie zusätzliche Kosten verursacht, die auch Anwendungen schaden würden, die auf ein gutes Verhalten geachtet haben.
  • Stellen Sie sicher, dass der Texturatlas verwendet wird. Die Elemente Image und BorderImage verwenden ihn, sofern das Bild nicht zu groß ist. Für Texturen, die in C++ erstellt wurden, übergeben Sie QQuickWindow::TextureCanUseAtlas beim Aufruf von QQuickWindow::createTexture(). Durch das Setzen der Umgebungsvariablen QSG_ATLAS_OVERLAY werden alle Atlas-Texturen eingefärbt, so dass sie in der Anwendung leicht identifizierbar sind.
  • Verwenden Sie nach Möglichkeit undurchsichtige Primitive. Opake Primitive lassen sich im Renderer schneller verarbeiten und auf der GPU schneller zeichnen. PNG-Dateien haben zum Beispiel oft einen Alphakanal, obwohl jedes Pixel vollständig undurchsichtig ist. JPG-Dateien sind immer undurchsichtig. Wenn Sie einem QQuickImageProvider Bilder zur Verfügung stellen oder Bilder mit QQuickWindow::createTextureFromImage() erstellen, lassen Sie das Bild nach Möglichkeit QImage::Format_RGB32 haben.
  • Beachten Sie, dass sich überschneidende zusammengesetzte Elemente, wie in der obigen Abbildung, nicht zusammengefügt werden können.
  • Das Ausschneiden unterbricht die Stapelverarbeitung. Verwenden Sie niemals pro Artikel, innerhalb von Tabellenzellen, Artikeldelegierten oder ähnlichem. Anstatt Text auszuschneiden, verwenden Sie Eliding. Anstatt ein Bild zu beschneiden, erstellen Sie eine QQuickImageProvider, die ein beschnittenes Bild zurückgibt.
  • Batching funktioniert nur bei 16-Bit-Indizes. Alle eingebauten Elemente verwenden 16-Bit-Indizes, aber eine benutzerdefinierte Geometrie kann auch 32-Bit-Indizes verwenden.
  • Einige Materialflags verhindern das Batching, wobei das einschränkendste QSGMaterial::RequiresFullMatrix ist, das jegliches Batching verhindert.
  • Anwendungen mit einem monochromen Hintergrund sollten diesen mit QQuickWindow::setColor() setzen, anstatt ein Rectangle-Element der obersten Ebene zu verwenden. QQuickWindow::setColor() wird in einem Aufruf von glClear() verwendet, was potenziell schneller ist.
  • Mipmapped Image-Elemente werden nicht im globalen Atlas platziert und werden nicht gebündelt.
  • Ein Fehler im OpenGL-Treiber im Zusammenhang mit dem Rücklesen von Framebuffer-Objekten (FBO) kann gerenderte Glyphen beschädigen. Wenn Sie die Umgebungsvariable QML_USE_GLYPHCACHE_WORKAROUND setzen, behält Qt eine zusätzliche Kopie der Glyphe im RAM. Dies bedeutet, dass die Leistung beim Zeichnen von Glyphen, die zuvor noch nicht gezeichnet wurden, etwas geringer ist, da Qt auf die zusätzliche Kopie über die CPU zugreift. Es bedeutet auch, dass der Glyphen-Cache doppelt so viel Speicher benötigt. Die Qualität wird hierdurch nicht beeinträchtigt.

Wenn eine Anwendung schlecht abschneidet, stellen Sie sicher, dass das Rendering tatsächlich der Flaschenhals ist. Verwenden Sie einen Profiler! Die Umgebungsvariable QSG_RENDER_TIMING=1 gibt eine Reihe nützlicher Timing-Parameter aus, die bei der Lokalisierung eines Problems hilfreich sein können.

Visualisierung

Um die verschiedenen Aspekte des Standard-Renderers des Szenegraphs zu visualisieren, kann die Umgebungsvariable QSG_VISUALIZE auf einen der Werte gesetzt werden, die in den einzelnen Abschnitten unten beschrieben werden. Der folgende QML-Code enthält Beispiele für die Ausgabe einiger der Variablen:

import QtQuick 2.2

Rectangle {
    width: 200
    height: 140

    ListView {
        id: clippedList
        x: 20
        y: 20
        width: 70
        height: 100
        clip: true
        model: ["Item A", "Item B", "Item C", "Item D"]

        delegate: Rectangle {
            color: "lightblue"
            width: parent.width
            height: 25

            Text {
                text: modelData
                anchors.fill: parent
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
        }
    }

    ListView {
        id: clippedDelegateList
        x: clippedList.x + clippedList.width + 20
        y: 20
        width: 70
        height: 100
        clip: true
        model: ["Item A", "Item B", "Item C", "Item D"]

        delegate: Rectangle {
            color: "lightblue"
            width: parent.width
            height: 25
            clip: true

            Text {
                text: modelData
                anchors.fill: parent
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
        }
    }
}

Für die ListView auf der linken Seite setzen wir die Eigenschaft clip auf true. Für die ListView auf der rechten Seite setzen wir außerdem die Eigenschaft clip jedes Delegaten auf true, um die Auswirkungen des Clippings auf die Stapelverarbeitung zu veranschaulichen.

"Original"

Original

Hinweis: Die visualisierten Elemente berücksichtigen keine Beschneidung, und die Rendering-Reihenfolge ist willkürlich.

Visualisierung von Chargen

Wenn Sie QSG_VISUALIZE auf batches setzen, werden Chargen im Renderer visualisiert. Zusammengeführte Chargen werden mit einer Volltonfarbe und nicht zusammengeführte Chargen mit einem diagonalen Linienmuster gezeichnet. Wenige eindeutige Farben bedeuten eine gute Stapelung. Nicht zusammengefasste Stapel sind schlecht, wenn sie viele einzelne Knoten enthalten.

"batches"

QSG_VISUALIZE=batches

Visualisierung des Clippings

Wenn Sie QSG_VISUALIZE auf clip setzen, werden rote Bereiche oben auf der Szene gezeichnet, um das Abschneiden anzuzeigen. Da Qt Quick Elemente standardmäßig nicht beschnitten werden, wird normalerweise keine Beschneidung angezeigt.

QSG_VISUALIZE=clip

Visualisierung von Änderungen

Die Einstellung QSG_VISUALIZE bis changes visualisiert Änderungen im Renderer. Änderungen im Szenegraphen werden durch ein blinkendes Overlay in einer zufälligen Farbe visualisiert. Änderungen an einem Primitiv werden mit einer Volltonfarbe visualisiert, während Änderungen an einem Vorgänger, wie z. B. Matrix- oder Deckkraftänderungen, mit einem Muster visualisiert werden.

Visualisierung von Overdraw

Wenn Sie QSG_VISUALIZE auf overdraw setzen, wird die Überzeichnung im Renderer visualisiert. Visualisieren Sie alle Elemente in 3D, um Überzeichnungen hervorzuheben. Dieser Modus kann auch verwendet werden, um Geometrie außerhalb des Ansichtsfensters bis zu einem gewissen Grad zu erkennen. Undurchsichtige Elemente werden mit einem grünen Farbton gerendert, während durchsichtige Elemente mit einem roten Farbton gerendert werden. Die Bounding Box für das Ansichtsfenster wird in Blau gerendert. Undurchsichtiger Inhalt ist für den Szenegraphen einfacher zu verarbeiten und wird normalerweise schneller gerendert.

Beachten Sie, dass das Wurzelrechteck im obigen Code überflüssig ist, da das Fenster ebenfalls weiß ist, so dass das Zeichnen des Rechtecks in diesem Fall eine Verschwendung von Ressourcen darstellt. Das Ändern in ein Item kann einen leichten Leistungsschub bringen.

"overdraw-1"

"overdraw-2"

QSG_VISUALIZE=overdraw

Rendering über das Qt Rendering Hardware Interface

Ab Qt 6.0 wird die Standardanpassung immer über eine Grafikabstraktionsschicht, das Qt Rendering Hardware Interface (RHI), gerendert, das vom Qt GUI Modul bereitgestellt wird. Das bedeutet, dass im Gegensatz zu Qt 5 keine direkten OpenGL-Aufrufe durch den Szenegraphen erfolgen. Stattdessen werden Ressourcen- und Zeichenbefehle mit Hilfe der RHI-APIs aufgezeichnet, die dann den Befehlsstrom in OpenGL-, Vulkan-, Metal- oder Direct 3D-Aufrufe übersetzen. Auch die Shader-Verarbeitung wird vereinheitlicht, indem der Shader-Code einmal geschrieben, nach SPIR-V kompiliert und dann in die für die verschiedenen Grafik-APIs geeignete Sprache übersetzt wird.

Um das Verhalten zu steuern, können die folgenden Umgebungsvariablen verwendet werden:

UmgebungsvariableMögliche WerteBeschreibung
QSG_RHI_BACKENDvulkan, metal, opengl, d3d11, d3d12Fordert das spezifische RHI-Backend an. Standardmäßig wird die angestrebte Grafik-API auf der Grundlage der Plattform ausgewählt, sofern sie nicht durch diese Variable oder die entsprechenden C++-APIs überschrieben wird. Die Standardeinstellungen sind derzeit Direct3D 11 für Windows, Metal für macOS und OpenGL für andere Plattformen.
QSG_INFO1Wie beim OpenGL-basierten Rendering-Pfad ermöglicht die Einstellung dieser Variable die Ausgabe von Systeminformationen bei der Initialisierung des Qt Quick Szenegraphen. Dies kann bei der Fehlersuche sehr nützlich sein.
QSG_RHI_DEBUG_LAYER1Falls zutreffend (Vulkan, Direct3D), aktiviert die Debug- oder Validierungsebenen der Grafik-API-Implementierung, falls verfügbar, entweder auf dem Grafikgerät oder dem Instanzobjekt. Für Metal unter macOS, setzen Sie stattdessen die Umgebungsvariable METAL_DEVICE_WRAPPER_TYPE=1.
QSG_RHI_PREFER_SOFTWARE_RENDERER1Fordert die Auswahl eines Adapters oder physischen Geräts an, das softwarebasierte Rasterung verwendet. Gilt nur, wenn die zugrundeliegende API die Aufzählung von Adaptern unterstützt (z. B. Direct3D oder Vulkan), und wird ansonsten ignoriert.

Anwendungen, die immer mit einer bestimmten Grafik-API laufen wollen, können dies auch über C++ anfordern. Zum Beispiel erzwingt der folgende Aufruf in main() vor der Konstruktion von QQuickWindow die Verwendung von Vulkan (und schlägt andernfalls fehl):

QQuickWindow::setGraphicsApi(QSGRendererInterface::Vulkan);

Siehe QSGRendererInterface::GraphicsApi. Die Enum-Werte OpenGL, Vulkan, Metal, Direct3D11, Direct3D12 sind in der Wirkung gleichbedeutend mit der Ausführung von QSG_RHI_BACKEND mit dem entsprechenden String-Schlüssel.

Alle QRhi Backends wählen den Standard-GPU-Adapter oder das physikalische Gerät des Systems, es sei denn, sie werden durch QSG_RHI_PREFER_SOFTWARE_RENDERER oder eine Backend-spezifische Variable wie QT_D3D_ADAPTER_INDEX oder QT_VK_PHYSICAL_DEVICE_INDEX überschrieben. Zurzeit ist keine weitere Konfigurierbarkeit des Adapters vorgesehen.

Ab Qt 6.5 sind einige der Einstellungen, die zuvor nur als Umgebungsvariablen verfügbar waren, als C++ APIs in QQuickGraphicsConfiguration verfügbar. Zum Beispiel sind das Setzen von QSG_RHI_DEBUG_LAYER und der Aufruf von setDebugLayer(true) gleichwertig.

© 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.