QQuickRenderControl RHIの例

Qt Quick シーンをQRhiTexture にレンダリングする方法を示します。

この例では、レンダリングがQRhiTexture にリダイレクトされるQt Quick シーンをセットアップする方法を示します。アプリケーションは、各フレームから得られるテクスチャを自由に扱うことができます。この例は、QWidget ベースのアプリケーションで、画像データのリードバックを実行し、収集されたフレームごとのレンダリングを、それぞれの CPU および GPU ベースのタイミング情報とともに表示します。

Qt の 3D グラフィックス API を抽象化することで、この例は特定のグラフィックス API に縛られません。起動時に、サポートされる可能性のあるプラットフォームの3D APIを示すダイアログが表示されます。

    QDialog apiSelect;
    QVBoxLayout *selLayout = new QVBoxLayout;
    selLayout->addWidget(new QLabel(QObject::tr("Select graphics API to use")));
    QListWidget *apiList = new QListWidget;
    QVarLengthArray<QSGRendererInterface::GraphicsApi, 5> apiValues;
#ifdef Q_OS_WIN
    apiList->addItem("Direct3D 11");
    apiValues.append(QSGRendererInterface::Direct3D11);
    apiList->addItem("Direct3D 12");
    apiValues.append(QSGRendererInterface::Direct3D12);
#endif
#if QT_CONFIG(metal)
    apiList->addItem("Metal");
    apiValues.append(QSGRendererInterface::Metal);
#endif
#if QT_CONFIG(vulkan)
    apiList->addItem("Vulkan");
    apiValues.append(QSGRendererInterface::Vulkan);
#endif
#if QT_CONFIG(opengl)
    apiList->addItem("OpenGL / OpenGL ES");
    apiValues.append(QSGRendererInterface::OpenGL);
#endif
    if (apiValues.isEmpty()) {
        QMessageBox::critical(nullptr, QObject::tr("No 3D graphics API"), QObject::tr("No 3D graphics APIs are supported in this Qt build"));
        return 1;
    }

注意: すべての選択が、特定のプラットフォームで機能することを保証するものではありません。

選択が行われると、QMLファイルがロードされます。しかし、単純にQQuickView インスタンスを作成し、show ()するわけではありません。むしろ、Qt Quick シーンを管理するQQuickWindow は決して画面上に表示されません。その代わりに、アプリケーションはQQuickRenderControl を介して、いつ、どこにレンダリングするかを制御します。

voidMainWindow::load(constQStringm_renderControl.reset(newQQuickRenderControl); m_scene.reset(newQQuickWindow(m_renderControl.get()));// QRhiCommandBuffer の lastCompletedGpuTime() を有効にします。   QQuickGraphicsConfigurationconfig; config.setTimestamps(true); m_scene->setGraphicsConfiguration(config);#if QT_CONFIG(vulkan) if(m_scene->graphicsApi()==QSGRendererInterface::Vulkan) m_scene->setVulkanInstance(m_vulkanInstance);#endifm_qmlEngine.reset(newQQmlEngine); m_qmlComponent.reset(newQQmlComponent(m_qmlEngine.get()QUrl::fromLocalFile(filename)));if(m_qmlComponent->isError()) {for(constQQmlErrorerror: m_qmlComponent->errors()) の場合            qWarning() << error.url() << error.line() << error;
        QMessageBox::critical(this,tr("Cannot load QML scene"),tr("Failed to load %1").arg(filename)); reset();return; }

オブジェクトツリーがインスタンス化されると、ルートアイテム(Rectangle )がクエリされ、そのサイズが有効であることが確認された後、プロパゲートされます。

注: オブジェクトツリー内でWindow 要素を使用するシーンはサポートされていません。

    QObject*rootObject =  m_qmlComponent->create();if(m_qmlComponent->isError()) {for(constQQmlError&error: m_qmlComponent->errors()) の場合。            qWarning() << error.url() << error.line() << error;
        QMessageBox::critical(this,tr("Cannot load QML scene"),tr("Failed to create component")); reset();return; }.  QQuickItem*rootItem =  qobject_cast<QQuickItem*>(rootObject);if(!rootItem) {// ルートオブジェクトがWindowだった場合、画面上のウィンドウを取り除く if(QQuickWindow*w =  qobject_cast<QQuickWindow*>(rootObject)) wを削除するQMessageBox::critical(this,tr("Invalid root item in QML scene"),tr("ルートオブジェクトはQQuickItemではありません。これがWindowを含むシーンである場合、そのようなシーンはサポートされていません。"); reset();return; }if(rootItem->size().width()< 16) rootItem->setSize()QSizeF(640, 360)); m_scene->contentItem()->setSize(rootItem->size()); m_scene->setGeometry(0, 0,  rootItem->size()), rootItem->size().width() < 16)width(),  rootItem->height()); rootItem->setParentItem(m_scene->contentItem()); m_statusMsg->setText(tr("QML scene loaded"));

この時点では、レンダリングリソースは初期化されていません。QRhi 、次のステップでインスタンス化されます。これが、Vulkan、Metal、Direct 3Dなどのレンダリングシステムをセットアップするトリガーとなります。

    const bool initSuccess = m_renderControl->initialize();
    if (!initSuccess) {
        QMessageBox::critical(this, tr("Cannot initialize renderer"), tr("QQuickRenderControl::initialize() failed"));
        reset();
        return;
    }

    const QSGRendererInterface::GraphicsApi api = m_scene->rendererInterface()->graphicsApi();
    switch (api) {
    case QSGRendererInterface::OpenGL:
        m_apiMsg->setText(tr("OpenGL"));
        break;
    case QSGRendererInterface::Direct3D11:
        m_apiMsg->setText(tr("D3D11"));
        break;
    case QSGRendererInterface::Direct3D12:
        m_apiMsg->setText(tr("D3D12"));
        break;
    case QSGRendererInterface::Vulkan:
        m_apiMsg->setText(tr("Vulkan"));
        break;
    case QSGRendererInterface::Metal:
        m_apiMsg->setText(tr("Metal"));
        break;
    default:
        m_apiMsg->setText(tr("Unknown 3D API"));
        break;
    }

    QRhi *rhi = m_renderControl->rhi();
    if (!rhi) {
        QMessageBox::critical(this, tr("Cannot render"), tr("No QRhi from QQuickRenderControl"));
        reset();
        return;
    }

    m_driverInfoMsg->setText(QString::fromUtf8(rhi->driverInfo().deviceName));

注意: このアプリケーションでは、Qt がQRhi のインスタンスを作成するモデルを使用しています。これは唯一の可能なアプローチではありません。アプリケーションが独自のQRhi (OpenGL コンテキスト、Vulkan デバイスなど)を保持している場合、Qt Quick に既存のQRhi を採用して使用するように要求することができます。これは、上記のスニペットでQQuickGraphicsConfiguration が設定される方法と同様に、QQuickGraphicsDevice::fromRhi() によって作成されたQQuickGraphicsDeviceQQuickWindow に渡すことによって行われます。たとえば、QRhiWidget でレンダリングされたテクスチャQt Quick を使用したい場合を考えてみましょう。この場合、Qt Quick に独自のテクスチャを作成させるのではなく、QRhiWidgetQRhiQt Quick に渡す必要があります。

QQuickRenderControl::initialize() が成功すれば、レンダラーが稼動し、準備が整ったことになる。そのためには、レンダリングするカラー・バッファが必要だ。

QQuickRenderTarget は軽量な暗黙的に共有されるクラスで、テクスチャやレンダーターゲットなどを記述するさまざまなネイティブオブジェクトや オブジェクトのセットを保持します(所有はしません)。 (画面上に表示されていない があることを思い出してください)で ()を呼び出すと、 シーングラフのレンダリングがアプリケーションによって提供されたテクスチャにリダイレクトされます。 (OpenGLテクスチャIDやVkImageオブジェクトのようなネイティブ3D APIオブジェクトではなく)を扱う場合、アプリケーションは を設定し、 ()を介して 。QRhi QQuickWindow QQuickWindow setRenderTarget Qt Quick QRhi QRhiTextureRenderTarget QQuickRenderTarget::fromRhiRenderTarget Qt Quick

    const QSize pixelSize = rootItem->size().toSize(); // no scaling, i.e. the item size is in pixels

    m_texture.reset(rhi->newTexture(QRhiTexture::RGBA8, pixelSize, 1,
                                    QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
    if (!m_texture->create()) {
        QMessageBox::critical(this, tr("Cannot render"), tr("Cannot create texture object"));
        reset();
        return;
    }

    m_ds.reset(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, 1));
    if (!m_ds->create()) {
        QMessageBox::critical(this, tr("Cannot render"), tr("Cannot create depth-stencil buffer"));
        reset();
        return;
    }

    QRhiTextureRenderTargetDescription rtDesc(QRhiColorAttachment(m_texture.get()));
    rtDesc.setDepthStencilBuffer(m_ds.get());
    m_rt.reset(rhi->newTextureRenderTarget(rtDesc));
    m_rpDesc.reset(m_rt->newCompatibleRenderPassDescriptor());
    m_rt->setRenderPassDescriptor(m_rpDesc.get());
    if (!m_rt->create()) {
        QMessageBox::critical(this, tr("Cannot render"), tr("Cannot create render target"));
        reset();
        return;
    }

    m_scene->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(m_rt.get()));

注: レンダリング時に、これらのバッファと深度およびステンシルテストの両方が、Qt Quick シーングラフによって使用される可能性があるため、Qt Quick には常に深度-ステンシルバッファを提供します。

メインのレンダリングループは次のとおりです。これは、画像の GPU->CPU リードバックを実行する方法も示しています。QImage が利用可能になると、それに応じてQWidget ベースのユーザー・インターフェイスが更新されます。ここでは、その詳細については省略します。

この例では、CPUとGPUでフレームをレンダリングするコストを測定する簡単な方法も示しています。オフスクリーンでレンダリングされたフレームは、QRhi の内部動作があるため、これに適しています。この動作は、そうでなければ非同期(後続のフレームをレンダリングするときにのみ完了するという意味で)である操作が、QRhi::endOffscreenFrame ()(すなわち、QQuickRenderControl::endFrame ())が返された時点で準備ができていることが保証されることを意味します。私たちはテクスチャを読み返すときにこの知識を使いますが、それはGPUのタイムスタンプにも当てはまります。そのため、アプリケーションは各フレームの GPU 時間を表示することができ、同時にその時間が実際にその特定のフレームを指している(それ以前のものではない)ことを保証することができます。GPU タイミングの詳細についてはlastCompletedGpuTime() を参照してください。CPU 側のタイミングは、QElapsedTimer を使って取得します。

    QElapsedTimer cpuTimer;
    cpuTimer.start();

    m_renderControl->polishItems();

    m_renderControl->beginFrame();

    m_renderControl->sync();
    m_renderControl->render();

    QRhi *rhi = m_renderControl->rhi();
    QRhiReadbackResult readResult;
    QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
    readbackBatch->readBackTexture(m_texture.get(), &readResult);
    m_renderControl->commandBuffer()->resourceUpdate(readbackBatch);

    m_renderControl->endFrame();

    const double gpuRenderTimeMs = m_renderControl->commandBuffer()->lastCompletedGpuTime() * 1000.0;
    const double cpuRenderTimeMs = cpuTimer.nsecsElapsed() / 1000000.0;

    // m_renderControl->begin/endFrame() is based on QRhi's
    // begin/endOffscreenFrame() under the hood, meaning it does not do
    // pipelining, unlike swapchain-based frames, and therefore the readback is
    // guaranteed to complete once endFrame() returns.
    QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
                    readResult.pixelSize.width(), readResult.pixelSize.height(),
                    QImage::Format_RGBA8888_Premultiplied);
    QImage result;
    if (rhi->isYUpInFramebuffer())
        result = wrapperImage.mirrored();
    else
        result = wrapperImage.copy();

ひとつ重要なのは、Qt Quick アニメーションのステッピングです。経過時間の計測、通常のタイマー、またはプレゼンテーショ ンレートベースのスロットリングのいずれかを使ってアニメーションシステムを 駆動できるオンスクリーンウィンドウがないため、Qt Quick のレンダリングをリダイレクトすると、多くの場合、アニメーションの駆動をアプリケーショ ンが引き継ぐ必要があります。そうでなければ、アニメーションは普通のシステムタイマーに基づいて機能しますが、実際の経過時間は、オフスクリーンでレンダリングされたシーンが知覚することを期待されているものとは無関係であることがよくあります。タイトなループで5フレームを連続してレンダリングすることを考えてみよう。その5フレーム内のアニメーションがどのように動くかは、CPUがループの繰り返しを実行する速度に依存する。これはほとんど理想的ではありません。一貫したアニメーションを保証するには、カスタムのQAnimationDriverをインストールしてください。これは文書化されていない(しかし公開されている)APIで、上級者向けのものですが、ここでは簡単な使用例を示しています。

class AnimationDriver : public QAnimationDriver
{
public:
    AnimationDriver(QObject *parent = nullptr)
        : QAnimationDriver(parent),
          m_step(16)
    {
    }

    void setStep(int milliseconds)
    {
        m_step = milliseconds;
    }

    void advance() override
    {
        m_elapsed += m_step;
        advanceAnimation();
    }

    qint64 elapsed() const override
    {
        return m_elapsed;
    }

private:
    int m_step;
    qint64 m_elapsed = 0;
};

このアプリケーションにはQSlider 、アニメーションのステップ値をデフォルトの16ミリ秒から他の値に変更することができます。QAnimationDriverサブクラスのsetStep()関数の呼び出しに注目してください。

    QSlider *animSlider = new QSlider;
    animSlider->setOrientation(Qt::Horizontal);
    animSlider->setMinimum(1);
    animSlider->setMaximum(1000);
    QLabel *animLabel = new QLabel;
    QObject::connect(animSlider, &QSlider::valueChanged, animSlider, [this, animLabel, animSlider] {
        if (m_animationDriver)
            m_animationDriver->setStep(animSlider->value());
        animLabel->setText(tr("Simulated elapsed time per frame: %1 ms").arg(animSlider->value()));
    });
    animSlider->setValue(16);
    QCheckBox *animCheckBox = new QCheckBox(tr("Custom animation driver"));
    animCheckBox->setToolTip(tr("Note: Installing the custom animation driver makes widget drawing unreliable, depending on the platform.\n"
                                "This is due to widgets themselves relying on QPropertyAnimation and similar, which are driven by the same QAnimationDriver.\n"
                                "In any case, the functionality of the widgets are not affected, just the rendering may lag behind.\n"
                                "When not checked, Qt Quick animations advance based on the system time, i.e. the time elapsed since the last press of the Next button."));
    QObject::connect(animCheckBox, &QCheckBox::checkStateChanged, animCheckBox, [this, animCheckBox, animSlider, animLabel] {
        if (animCheckBox->isChecked()) {
            animSlider->setEnabled(true);
            animLabel->setEnabled(true);
            m_animationDriver = new AnimationDriver(this);
            m_animationDriver->install();
            m_animationDriver->setStep(animSlider->value());
        } else {
            animSlider->setEnabled(false);
            animLabel->setEnabled(false);
            delete m_animationDriver;
            m_animationDriver = nullptr;
        }
    });
    animSlider->setEnabled(false);
    animLabel->setEnabled(false);
    controlLayout->addWidget(animCheckBox);
    controlLayout->addWidget(animLabel);
    controlLayout->addWidget(animSlider);

Note: カスタムアニメーションドライバのインストールは、animCheckBox のチェックボックスでオプションになっています。これにより、カスタム・アニメーション・ドライバをインストールした場合としなかった場合の効果を比較することができます。さらに、プラットフォームによっては(そしておそらくテーマによっては)、カスタムドライバを有効にすると、ウィジェットの描画に遅延が生じることがあります。というのも、いくつかのウィジェットのアニメーション(例えば、QPushButtonQCheckBox のハイライト)がQPropertyAnimation などで管理されている場合、それらのアニメーションは同じ QAnimationDriver によって駆動され、ボタンをクリックして新しいフレームが要求されるまで進まないからです。

アニメーションを進めるには、単純に advance() を呼び出すことで、各フレームの前(つまり、QQuickRenderControl::beginFrame() 呼び出しの前)に行われます:

void MainWindow::stepAnimations()
{
    if (m_animationDriver) {
        // Now the Qt Quick scene will think that <slider value> milliseconds have
        // elapsed and update animations accordingly when doing the next frame.
        m_animationDriver->advance();
    }
}

プロジェクト例 @ code.qt.io

QRhi,QQuickRenderControl,QQuickWindowも参照の こと。

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