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

このサンプルでは、Qt Quick シーンのレンダリング出力をQRhiTexture にリダイレクトする設定方法を示します。これにより、アプリケーションは各フレームから生成されたテクスチャを自由に処理できるようになります。このサンプルは、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;
}注: 特定のプラットフォームにおいて、選択したすべての3D APIが正常に動作するとは限りません。
選択が完了すると、QMLファイルが読み込まれます。ただし、単にQQuickView インスタンスを作成してshow()を呼び出すだけではありません。むしろ、Qt Quick シーンを管理するQQuickWindow は画面上に表示されません。その代わりに、アプリケーションはQQuickRenderControl を通じて、いつ、どこにレンダリングするかを制御します。
voidMainWindow::load(constQString&filename)
{
reset();
m_renderControl.reset(newQQuickRenderControl);
m_scene.reset(newQQuickWindow(m_renderControl.get()));
// 基盤となる3D APIがサポートしている場合、QRhiCommandBufferでlastCompletedGpuTime()を有効にする
QQuickGraphicsConfiguration config;
config.setTimestamps(true);
m_scene->setGraphicsConfiguration(config);
#if QT_CONFIG(vulkan)
if(m_scene->graphicsApi()==QSGRendererInterface::Vulkan)
m_scene->setVulkanInstance(m_vulkanInstance);
#endif
m_qmlEngine.reset(newQQmlEngine);
m_qmlComponent.reset(newQQmlComponent(m_qmlEngine.get(),QUrl::fromLocalFile(filename)));
if(m_qmlComponent->isError()) {
for(constQQmlError&error: m_qmlComponent->errors())
qWarning() << error.url() << error.line() << error;
QMessageBox::critical(this,tr("QMLシーンを読み込めません"),tr("%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("QMLシーンを読み込めません"),tr("コンポーネントの作成に失敗しました"));
reset();
return;
}
QQuickItem*rootItem =qobject_cast<QQuickItem*>(rootObject);
if(!rootItem) {
// ルートオブジェクトが Window だった場合、画面上のウィンドウを削除する
if(QQuickWindow*w =qobject_cast<QQuickWindow*>(rootObject))
deletew;
QMessageBox::critical(this,
tr("QMLシーン内のルートアイテムが無効です"),
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->width(), rootItem->height());
rootItem->setParentItem(m_scene->contentItem());
m_statusMsg->setText(tr("QMLシーンを読み込みました"));この時点では、レンダリングリソースは初期化されておらず、つまりネイティブの3DグラフィックスAPIによる処理はまだ何も行われていません。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 を採用して使用するよう要求することができます。 これは、QQuickGraphicsDevice::fromRhi() で作成されたQQuickGraphicsDevice をQQuickWindow に渡し行うもので、上記のスニペットでQQuickGraphicsConfiguration が設定される方法と同様です。例えば、Qt Quick でレンダリングされたテクスチャをQRhiWidget で使用したい場合を考えてみましょう。その場合、QRhiWidget のQRhi をQt Quick に渡す必要があり、Qt Quick に独自のテクスチャを作成させるのではなく、既存のテクスチャを使用することになります。
QQuickRenderControl::initialize()が成功すると、レンダラーは稼働状態となり、使用可能な状態になります。そのためには、レンダリングを行うためのカラーバッファが必要です。
QQuickRenderTarget は、テクスチャやレンダリングターゲットなどを記述する、さまざまなネイティブオブジェクトやQRhi オブジェクトのセットを保持する(ただし所有はしない)軽量な暗黙的共有クラスです。QQuickWindow に対してsetRenderTarget()を呼び出す(画面上には表示されないQQuickWindow が存在することを忘れないでください)ことで、Qt Quick シーングラフのレンダリングが、アプリケーションが提供したテクスチャへリダイレクトされるようになります。QRhi を使用する場合(OpenGLのテクスチャIDやVkImageオブジェクトなどのネイティブ3D APIオブジェクトを使用しない場合)、アプリケーションは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での1フレームのレンダリングにかかるコストを測定する簡単な方法も示しています。 オフスクリーンでレンダリングされたフレームは、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.flipped();
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);注: animCheckBox のチェックボックスにより、カスタムアニメーションドライバーのインストールは 任意となっています。これにより、カスタムアニメーションドライバーをインストールした場合とインストールしていない場合の効果を比較することができます。また、一部のプラットフォーム(およびテーマによっては)では、カスタムドライバーを有効にすると、ウィジェットの描画に遅延が生じる場合があります。 これは想定内の挙動です。なぜなら、一部のウィジェットのアニメーション(例:QPushButton やQCheckBox のハイライト)がQPropertyAnimation などを介して管理されている場合、それらのアニメーションは同じQAnimationDriverによって駆動され、ボタンをクリックして新しいフレームが要求されるまで進行しないからです。
アニメーションの進行は、各フレームの開始前(つまり、QQuickRenderControl::beginFrame()の呼び出し前)に、単にadvance()を呼び出すだけで実行されます:
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();
}
}QRhi 、QQuickRenderControl 、およびQQuickWindowも参照してください 。
© 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.