Einfaches RHI-Widget-Beispiel
Zeigt, wie man ein Dreieck mit „ QRhi “ rendert, der Qt 3D-API und der Abstraktionsschicht für die Shading-Sprache.

Screenshot des Beispiels „Einfaches RHI-Widget“
Dieses Beispiel ist in vielerlei Hinsicht das Pendant zum RHI-Fenster-Beispiel in der QWidget -Welt. Die Unterklasse „ QRhiWidget “ in dieser Anwendung rendert ein einzelnes Dreieck unter Verwendung einer einfachen Grafikpipeline mit grundlegenden Vertex- und Fragment-Shadern. Im Gegensatz zur reinen, auf „ QWindow “ basierenden Anwendung muss sich dieses Beispiel nicht um Details auf niedrigerer Ebene kümmern, wie beispielsweise das Einrichten des Fensters und des „ QRhi “ oder den Umgang mit Swapchain- und Fensterereignissen, da dies hier vom „ QWidget “-Framework übernommen wird. Die Instanz der Unterklasse „ QRhiWidget “ wird einem „ QVBoxLayout “ hinzugefügt. Um das Beispiel möglichst einfach und kompakt zu halten, werden keine weiteren Widgets oder 3D-Inhalte eingeführt.
Sobald eine Instanz von ` ExampleRhiWidget`, einer Unterklasse von ` QRhiWidget `, zur Kindhierarchie eines Widgets der obersten Ebene hinzugefügt wird, wird das entsprechende Fenster automatisch zu einem mit Direct 3D, Vulkan, Metal oder OpenGL gerenderten Fenster. Der mit „ QPainter “ gerenderte Widget-Inhalt, d. h. alles, was kein „ QRhiWidget “, „ QOpenGLWidget “ oder „ QQuickWidget “ ist, wird dann in eine Textur hochgeladen, während die genannten speziellen Widgets jeweils in eine Textur gerendert werden. Die resultierende Menge an „ textures “ wird vom Backingstore des Widgets der obersten Ebene zusammengesetzt.
Struktur und main()
Die Funktion „ main() “ ist recht einfach. Das Widget der obersten Ebene hat standardmäßig eine Größe von 720p (diese Größe ist in logischen Einheiten angegeben; die tatsächliche Pixelgröße kann je nach „ scale factor “ abweichen). Das Fenster ist in der Größe anpassbar. „ QRhiWidget “ erleichtert die Implementierung von Unterklassen, die die Größenanpassung des Widgets aufgrund von Änderungen der Fenstergröße oder des Layouts korrekt handhaben.
int main(int argc, char **argv)
{
QApplication app(argc, argv);
ExampleRhiWidget *rhiWidget = new ExampleRhiWidget;
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(rhiWidget);
QWidget w;
w.setLayout(layout);
w.resize(1280, 720);
w.show();
return app.exec();
}Die Unterklasse „ QRhiWidget “ implementiert die beiden virtuellen Methoden neu: „ initialize()“ und „ render()“. initialize() wird mindestens einmal vor render() aufgerufen, wird aber auch bei einer Reihe wichtiger Änderungen aufgerufen, beispielsweise wenn die Hintergrundtextur des Widgets aufgrund einer sich ändernden Widgetgröße neu erstellt wird, wenn sich die Render-Target-Parameter ändern oder wenn das Widget aufgrund des Wechsels in ein neues Top-Level-Fenster zu einem neuen „ QRhi “ wechselt.
Hinweis: Im Gegensatz zum veralteten Modell „ initializeGL – resizeGL – paintGL “ aus „ QOpenGLWidget “ gibt es in „ QRhiWidget “ nur zwei virtuelle Methoden. Dies liegt daran, dass es mehr spezielle Ereignisse gibt, die möglicherweise berücksichtigt werden müssen, als nur die Größenänderung, z. B. bei der Neuzuordnung zu einem anderen Top-Level-Fenster. (Robuste Implementierungen von ` QOpenGLWidget ` mussten dies durch zusätzliche Verwaltung bewältigen, z. B. durch Verfolgung der Lebensdauer des zugehörigen ` QOpenGLContext `, was bedeutet, dass die drei virtuellen Methoden eigentlich nicht ausreichten.) Ein einfacheres Paar aus ` initialize ` und ` render`, bei dem ` initialize ` bei wichtigen Änderungen erneut aufgerufen wird, ist hierfür besser geeignet.
Die Instanz von ` QRhi ` gehört nicht zum Widget. Sie wird in ` initialize() ` und ` from the base class` abgefragt. Durch das Speichern als Member können Änderungen erkannt werden, wenn ` initialize() ` erneut aufgerufen wird. Grafikressourcen wie die Vertex- und Uniform-Buffer oder die Grafik-Pipeline unterliegen jedoch der Kontrolle von ` ExampleRhiWidget`.
#include <QRhiWidget>
#include <rhi/qrhi.h>
class ExampleRhiWidget : public QRhiWidget
{
public:
ExampleRhiWidget(QWidget *parent = nullptr) : QRhiWidget(parent) { }
void initialize(QRhiCommandBuffer *cb) override;
void render(QRhiCommandBuffer *cb) override;
private:
QRhi *m_rhi = nullptr;
std::unique_ptr<QRhiBuffer> m_vbuf;
std::unique_ptr<QRhiBuffer> m_ubuf;
std::unique_ptr<QRhiShaderResourceBindings> m_srb;
std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
QMatrix4x4 m_viewProjection;
float m_rotation = 0.0f;
};Damit die Anweisung „ #include <rhi/qrhi.h> “ funktioniert, muss die Anwendung mit „ GuiPrivate “ (oder „ gui-private “ mit qmake) verknüpft sein. Weitere Details zur Kompatibilitätsgarantie der QRhi -API-Familie finden Sie unter QRhi.
CMakeLists.txt
target_link_libraries(simplerhiwidget PRIVATE
Qt6::Core
Qt6::Gui
Qt6::GuiPrivate
Qt6::Widgets
)Rendering-Einrichtung
In ` examplewidget.cpp ` nutzt die Widget-Implementierung eine Hilfsfunktion, um ein ` QShader `-Objekt aus einer ` .qsb `-Datei zu laden. Diese Anwendung wird mit vorbereiteten „ .qsb “-Dateien ausgeliefert, die über das Qt-Ressourcensystem in die ausführbare Datei eingebettet sind. Aufgrund von Modulabhängigkeiten (und da qmake weiterhin unterstützt wird) verwendet dieses Beispiel nicht die praktische CMake-Funktion „ qt_add_shaders() “, sondern liefert die „ .qsb “-Dateien als Teil des Quellcodes mit. In realen Anwendungen wird empfohlen, dies zu vermeiden und stattdessen die CMake-Integrationsfunktionen des Qt-Moduls „ Shader Tools “ (qt_add_shaders) zu nutzen. Unabhängig vom gewählten Ansatz erfolgt das Laden der mitgelieferten bzw. generierten „ .qsb “-Dateien im C++-Code auf dieselbe Weise.
static QShader getShader(const QString &name)
{
QFile f(name);
return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
}Sehen wir uns die Implementierung von `initialize()` an. Zunächst wird das ` QRhi `-Objekt abgefragt und für die spätere Verwendung gespeichert, auch um bei zukünftigen Aufrufen der Funktion einen Vergleich zu ermöglichen. Bei einer Nichtübereinstimmung (z. B. wenn das Widget zwischen Fenstern verschoben wird) wird die Neuerstellung der Grafikressourcen ausgelöst, indem ein entsprechendes Objekt – in diesem Fall das ` m_pipeline` – zerstört und auf null gesetzt wird. Das Beispiel demonstriert zwar nicht aktiv die Umverteilung zwischen Fenstern, ist aber darauf vorbereitet, diese zu handhaben. Es ist zudem darauf vorbereitet, eine sich ändernde Widget-Größe zu verarbeiten, die beim Ändern der Fenstergröße auftreten kann. Dies erfordert keine besondere Behandlung, da bei jedem solchen Vorgang „ initialize() “ aufgerufen wird und somit die Abfrage von „ renderTarget()->pixelSize() “ oder „ colorTexture()->pixelSize() “ stets die aktuellste Größe in Pixeln liefert. Worauf dieses Beispiel nicht vorbereitet ist, sind Änderungen an color buffer formats und multisample settings, da es stets nur die Standardwerte verwendet (RGBA8 und kein Multisample-Antialiasing).
void ExampleRhiWidget::initialize(QRhiCommandBuffer *cb)
{
if (m_rhi != rhi()) {
m_pipeline.reset();
m_rhi = rhi();
}Wenn die Grafikressourcen (neu) erstellt werden müssen, erfolgt dies in „ initialize() “ mithilfe von recht typischem, auf „ QRhi “ basierendem Code. Ein einzelner Vertex-Puffer mit den verschachtelten Positions- und Farbdaten der Scheitelpunkte reicht aus, während die Model-View-Projection-Matrix über einen Uniform-Puffer von 64 Byte (16 Floats) bereitgestellt wird. Der Uniform-Puffer ist die einzige für den Shader sichtbare Ressource und wird ausschließlich im Vertex-Shader verwendet. Die Grafikpipeline stützt sich auf zahlreiche Standardeinstellungen (z. B. Tiefenprüfung deaktiviert, Blending deaktiviert, Farbschreiben aktiviert, Face Culling deaktiviert, die Standardtopologie von Dreiecken usw.). Das Layout der Vertexdaten lautet x, y, r, g, b; der Stride beträgt somit 5 Floats, während das zweite Vertex-Eingabeattribut (die Farbe) einen Offset von 2 Floats aufweist ( x und y werden übersprungen). Jede Grafikpipeline muss einem QRhiRenderPassDescriptor zugeordnet sein. Dieser kann aus dem QRhiRenderTarget abgerufen werden, der von der Basisklasse verwaltet wird.
Hinweis: Dieses Beispiel setzt voraus, dass die Standardeinstellung von „ QRhiWidget “ unter „ autoRenderTarget “ auf „ true “ gesetzt ist. Aus diesem Grund muss das Renderziel nicht verwaltet werden, sondern es kann einfach das vorhandene abgefragt werden, indem „ renderTarget()“ aufgerufen wird.
if (!m_pipeline) {
m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
m_vbuf->create();
m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
m_ubuf->create();
m_srb.reset(m_rhi->newShaderResourceBindings());
m_srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, m_ubuf.get()),
});
m_srb->create();
m_pipeline.reset(m_rhi->newGraphicsPipeline());
m_pipeline->setShaderStages({
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/shader_assets/color.vert.qsb")) },
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/shader_assets/color.frag.qsb")) }
});
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 5 * sizeof(float) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
{ 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
});
m_pipeline->setVertexInputLayout(inputLayout);
m_pipeline->setShaderResourceBindings(m_srb.get());
m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
m_pipeline->create();
QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);
cb->resourceUpdate(resourceUpdates);
}Abschließend wird die Projektionsmatrix berechnet. Diese hängt von der Widget-Größe ab und wird daher bei jedem Aufruf der Funktionen bedingungslos durchgeführt.
Hinweis: Alle Berechnungen von Größe und Viewport sollten sich ausschließlich auf die Pixelgröße stützen, die von der als Farbpuffer dienenden Ressource abgefragt wird, da dies das tatsächliche Renderziel ist. Vermeiden Sie es, Größen, Viewports, Scheren usw. manuell auf der Grundlage der von QWidget gemeldeten Größe oder des Geräte-Pixelverhältnisses zu berechnen.
Hinweis: Die Projektionsmatrix enthält die „ correction matrix “ aus „ QRhi “, um Unterschiede zwischen 3D-APIs bei den normalisierten Gerätekordinaten zu berücksichtigen (z. B. Y nach unten vs. Y nach oben).
Eine Translation von -4 wird angewendet, um sicherzustellen, dass das Dreieck mit den Werten z von 0 sichtbar ist.
const QSize outputSize = renderTarget()->pixelSize();
m_viewProjection = m_rhi->clipSpaceCorrMatrix();
m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
m_viewProjection.translate(0, 0, -4);
}Rendering
Das Widget zeichnet einen einzigen Renderdurchlauf auf, der einen einzigen Draw-Call enthält.
Die im Initialisierungsschritt berechnete Ansichts-Projektionsmatrix wird mit der Modellmatrix kombiniert, bei der es sich in diesem Fall um eine einfache Drehung handelt. Die resultierende Matrix wird dann in den Uniform-Puffer geschrieben. Beachten Sie, wie „ resourceUpdates “ an „ beginPass()“ übergeben wird – dies ist eine Abkürzung, um den manuellen Aufruf von „ resourceUpdate()“ zu vermeiden.
void ExampleRhiWidget::render(QRhiCommandBuffer *cb)
{
QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
m_rotation += 1.0f;
QMatrix4x4 modelViewProjection = m_viewProjection;
modelViewProjection.rotate(m_rotation, 0, 1, 0);
resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData());Im Render-Durchlauf wird ein einzelner Draw-Call mit 3 Scheitelpunkten aufgezeichnet. Die im Initialisierungsschritt erstellte Grafikpipeline wird an den Befehlspuffer gebunden, und der Viewport wird so eingestellt, dass er das gesamte Widget abdeckt. Um den Uniform-Puffer für den (Vertex-)Shader sichtbar zu machen, wird ` setShaderResources()` ohne Argument aufgerufen, was bedeutet, dass das ` m_srb ` verwendet wird, da dieses bei der Erstellung der Pipeline mit dieser verknüpft wurde. In komplexeren Renderern ist es nicht ungewöhnlich, ein anderes „ QRhiShaderResourceBindings “-Objekt zu übergeben, solange dieses mit dem bei der Erstellung der Pipeline angegebenen Objekt „ layout-compatible “ ist. Es gibt keinen Indexpuffer, und es gibt eine einzige Vertexpuffer-Bindung (das einzelne Element in „ vbufBinding “ bezieht sich auf den einzelnen Eintrag in der Bindungsliste des „ QRhiVertexInputLayout “, der bei der Erstellung der Pipeline angegeben wurde).
const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates);
cb->setGraphicsPipeline(m_pipeline.get());
const QSize outputSize = renderTarget()->pixelSize();
cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
cb->setShaderResources();
const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
cb->setVertexInput(0, 1, &vbufBinding);
cb->draw(3);
cb->endPass();Sobald der Render-Durchlauf aufgezeichnet ist, wird ` update()` aufgerufen. Dies fordert einen neuen Frame an und wird verwendet, um sicherzustellen, dass das Widget kontinuierlich aktualisiert wird und das Dreieck rotierend erscheint. Der Rendering-Thread (in diesem Fall der Hauptthread) wird standardmäßig durch die Bildwiederholrate gedrosselt. Da dieses Beispiel kein richtiges Animationssystem enthält, nimmt die Drehgeschwindigkeit in jedem Frame zu, was bedeutet, dass sich das Dreieck auf Bildschirmen mit unterschiedlichen Bildwiederholraten mit unterschiedlichen Geschwindigkeiten dreht.
update();
}Siehe auch „ QRhi“ , „Cube RHI Widget Example“ und „RHI Window Example“.
© 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.