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`을 통해 렌더링 시점과 위치를 직접 제어합니다.
void MainWindow::load(const QString&filename)
{
reset();
m_renderControl.reset(new QQuickRenderControl);
m_scene.reset(new QQuickWindow(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(new QQmlEngine);
m_qmlComponent.reset(new QQmlComponent(m_qmlEngine.get(), QUrl::fromLocalFile(filename)));
if (m_qmlComponent->isError()) {
for (const QQmlError&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 (const QQmlError&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))
delete w;
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 에서 사용하고자 하는 경우를 생각해 봅시다. 이 경우 Qt Quick 가 자체적으로 생성하도록 두지 말고, QRhiWidget 의 QRhi 를 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에서 프레임 렌더링에 소요되는 비용을 측정하는 간단한 방법을 보여줍니다. 오프스크린으로 렌더링된 프레임은 특정 내부 ` 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;
};이 애플리케이션에는 애니메이션 단계 값을 기본값인 16밀리초에서 다른 값으로 변경하는 데 사용할 수 있는 ` QSlider `가 있습니다. 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.