QmlDesigner: Fix qml-renderer puppet

Fixed various issues with qml-renderer puppet mode:
- Delete objects properly
- Use smooth scaling
- Remove unnecessary code
- Fix brief flash on screen in case of Window root in qml
- Display error if qml fails to load

Task-number: QDS-14691
Change-Id: Ic8944a024ab5eb3c1ee0f0e7310f71af03fdb9e2
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Miikka Heikkinen
2025-02-10 16:12:53 +02:00
parent 16bf17d20d
commit e4fd19c59f
2 changed files with 66 additions and 63 deletions

View File

@@ -12,13 +12,9 @@
#endif #endif
#include <private/qquickdesignersupport_p.h> #include <private/qquickdesignersupport_p.h>
#include <private/qquickrendercontrol_p.h>
#include <private/qquickrendertarget_p.h>
#include <private/qrhi_p.h>
#include <QFileInfo> #include <QFileInfo>
#include <QQmlComponent> #include <QQmlComponent>
#include <QQmlEngine>
void QmlRenderer::initCoreApp() void QmlRenderer::initCoreApp()
{ {
@@ -32,24 +28,25 @@ void QmlRenderer::initCoreApp()
void QmlRenderer::populateParser() void QmlRenderer::populateParser()
{ {
m_argParser.addOptions({ m_argParser.addOptions({
{QStringList() << "i" << "importpath", {QStringList{"i", "importpath"},
"Prepend the given path to the import paths.", "Prepend the given path to the import paths.",
"path"}, "path"},
{QStringList() << "o" << "outfile", {QStringList{"o", "outfile"},
"Output image file path.", "Output image file path.",
"path"}, "path"},
// "h" is reserved arg for help, so use capital letters for height/width // "h" is reserved arg for help, so use capital letters for height/width
{QStringList() << "H" << "height", {QStringList{"H", "height"},
"Height of the final rendered image.", "Height of the final rendered image.",
"pixels"}, "pixels"},
{QStringList() << "W" << "width", {QStringList{"W", "width"},
"Width of the final rendered image.", "Width of the final rendered image.",
"pixels"}, "pixels"},
{QStringList() << "v" << "verbose", "Display additional output."} {QStringList{"v", "verbose"},
"Display additional output."}
}); });
m_argParser.addPositionalArgument("file", "QML file to render.", "file"); m_argParser.addPositionalArgument("file", "QML file to render.", "file");
@@ -104,29 +101,37 @@ bool QmlRenderer::setupRenderer()
QQuickDesignerSupport::activateDesignerMode(); QQuickDesignerSupport::activateDesignerMode();
QQmlEngine *engine = new QQmlEngine; m_engine = std::make_unique<QQmlEngine>();
for (const QString &path : std::as_const(m_importPaths)) for (const QString &path : std::as_const(m_importPaths))
engine->addImportPath(path); m_engine->addImportPath(path);
m_renderControl = new QQuickRenderControl; m_renderControl = std::make_unique<QQuickRenderControl>();
m_window = new QQuickWindow(m_renderControl); m_window = std::make_unique<QQuickWindow>(m_renderControl.get());
m_window->setDefaultAlphaBuffer(true); m_window->setDefaultAlphaBuffer(true);
m_window->setColor(Qt::transparent); m_window->setColor(Qt::transparent);
m_renderControl->initialize(); m_renderControl->initialize();
QQmlComponent component(engine); QQmlComponent component(m_engine.get());
component.loadUrl(QUrl::fromLocalFile(m_sourceFile)); component.loadUrl(QUrl::fromLocalFile(m_sourceFile));
if (component.isError()) {
error(QString("Failed to load url: %1").arg(m_sourceFile));
error(component.errorString());
return false;
}
QObject *renderObj = component.create(); QObject *renderObj = component.create();
if (renderObj) { if (renderObj) {
#ifdef QUICK3D_MODULE #ifdef QUICK3D_MODULE
QQuickItem *contentItem3D = nullptr; QQuickItem *contentItem3D = nullptr;
renderObj->setParent(m_window->contentItem());
if (qobject_cast<QQuick3DObject *>(renderObj)) { if (qobject_cast<QQuick3DObject *>(renderObj)) {
auto helper = new QmlDesigner::Internal::GeneralHelper(); m_helper = std::make_unique<QmlDesigner::Internal::GeneralHelper>();
engine->rootContext()->setContextProperty("_generalHelper", helper); m_engine->rootContext()->setContextProperty("_generalHelper", m_helper.get());
QQmlComponent component(engine); QQmlComponent component(m_engine.get());
component.loadUrl(QUrl("qrc:/qtquickplugin/mockfiles/qt6/ModelNode3DImageView.qml")); component.loadUrl(QUrl("qrc:/qtquickplugin/mockfiles/qt6/ModelNode3DImageView.qml"));
m_containerItem = qobject_cast<QQuickItem *>(component.create()); m_containerItem = qobject_cast<QQuickItem *>(component.create());
if (!m_containerItem) { if (!m_containerItem) {
@@ -167,14 +172,22 @@ bool QmlRenderer::setupRenderer()
} else if (auto renderWindow = qobject_cast<QQuickWindow *>(renderObj)) { } else if (auto renderWindow = qobject_cast<QQuickWindow *>(renderObj)) {
// Hack to render Window items: reparent window content to m_window->contentItem() // Hack to render Window items: reparent window content to m_window->contentItem()
m_renderSize = renderWindow->size(); m_renderSize = renderWindow->size();
renderWindow->setVisible(false);
m_containerItem = m_window->contentItem(); m_containerItem = m_window->contentItem();
// Suppress the original window.
// Offscreen position ensures we don't get even brief flash of it.
renderWindow->setPosition(-100000, -100000);
renderWindow->setVisible(false);
const QList<QQuickItem *> childItems = renderWindow->contentItem()->childItems(); const QList<QQuickItem *> childItems = renderWindow->contentItem()->childItems();
for (QQuickItem *item : childItems) for (QQuickItem *item : childItems) {
item->setParent(m_window->contentItem());
item->setParentItem(m_window->contentItem()); item->setParentItem(m_window->contentItem());
}
} else {
error("Invalid root object type.");
return false;
} }
if ((m_containerItem) && (contentItem3D || !m_is3D)) { if (m_containerItem && (contentItem3D || !m_is3D)) {
m_window->setGeometry(0, 0, m_renderSize.width(), m_renderSize.height()); m_window->setGeometry(0, 0, m_renderSize.width(), m_renderSize.height());
m_window->contentItem()->setSize(m_renderSize); m_window->contentItem()->setSize(m_renderSize);
m_containerItem->setSize(m_renderSize); m_containerItem->setSize(m_renderSize);
@@ -196,7 +209,7 @@ bool QmlRenderer::setupRenderer()
bool QmlRenderer::initRhi() bool QmlRenderer::initRhi()
{ {
if (!m_rhi) { if (!m_rhi) {
QQuickRenderControlPrivate *rd = QQuickRenderControlPrivate::get(m_renderControl); QQuickRenderControlPrivate *rd = QQuickRenderControlPrivate::get(m_renderControl.get());
m_rhi = rd->rhi; m_rhi = rd->rhi;
if (!m_rhi) { if (!m_rhi) {
error("Rhi is null."); error("Rhi is null.");
@@ -204,36 +217,31 @@ bool QmlRenderer::initRhi()
} }
} }
m_texTarget = nullptr; m_texture.reset(m_rhi->newTexture(QRhiTexture::RGBA8, m_renderSize, 1,
m_rpDesc = nullptr; QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
m_buffer = nullptr;
m_texture = nullptr;
m_texture = m_rhi->newTexture(QRhiTexture::RGBA8, m_renderSize, 1,
QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
if (!m_texture->create()) { if (!m_texture->create()) {
error("QRhiTexture creation failed."); error("QRhiTexture creation failed.");
return false; return false;
} }
m_buffer = m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_renderSize, 1); m_buffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_renderSize, 1));
if (!m_buffer->create()) { if (!m_buffer->create()) {
error("Depth/stencil buffer creation failed."); error("Depth/stencil buffer creation failed.");
return false; return false;
} }
QRhiTextureRenderTargetDescription rtDesc {QRhiColorAttachment(m_texture)}; QRhiTextureRenderTargetDescription rtDesc {QRhiColorAttachment(m_texture.get())};
rtDesc.setDepthStencilBuffer(m_buffer); rtDesc.setDepthStencilBuffer(m_buffer.get());
m_texTarget = m_rhi->newTextureRenderTarget(rtDesc); m_texTarget.reset(m_rhi->newTextureRenderTarget(rtDesc));
m_rpDesc = m_texTarget->newCompatibleRenderPassDescriptor(); m_rpDesc.reset(m_texTarget->newCompatibleRenderPassDescriptor());
m_texTarget->setRenderPassDescriptor(m_rpDesc); m_texTarget->setRenderPassDescriptor(m_rpDesc.get());
if (!m_texTarget->create()) { if (!m_texTarget->create()) {
error("Texture render target creation failed."); error("Texture render target creation failed.");
return false; return false;
} }
// redirect Qt Quick rendering into our texture // redirect Qt Quick rendering into our texture
m_window->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(m_texTarget)); m_window->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(m_texTarget.get()));
return true; return true;
} }
@@ -242,15 +250,6 @@ void QmlRenderer::render()
{ {
info(QString("Rendering: %1").arg(m_sourceFile)); info(QString("Rendering: %1").arg(m_sourceFile));
std::function<void (QQuickItem *)> updateNodesRecursive;
updateNodesRecursive = [&updateNodesRecursive](QQuickItem *item) {
const auto childItems = item->childItems();
for (QQuickItem *childItem : childItems)
updateNodesRecursive(childItem);
if (item->flags() & QQuickItem::ItemHasContents)
item->update();
};
QImage renderImage; QImage renderImage;
// Need to render fitted 3D views twice, first render updates spatial node geometries // Need to render fitted 3D views twice, first render updates spatial node geometries
@@ -259,7 +258,6 @@ void QmlRenderer::render()
if (m_fit3D && i == 1) if (m_fit3D && i == 1)
QMetaObject::invokeMethod(m_containerItem, "fitToViewPort", Qt::DirectConnection); QMetaObject::invokeMethod(m_containerItem, "fitToViewPort", Qt::DirectConnection);
updateNodesRecursive(m_containerItem);
m_renderControl->polishItems(); m_renderControl->polishItems();
m_renderControl->beginFrame(); m_renderControl->beginFrame();
m_renderControl->sync(); m_renderControl->sync();
@@ -277,14 +275,16 @@ void QmlRenderer::render()
readResult.pixelSize.width(), readResult.pixelSize.height(), readResult.pixelSize.width(), readResult.pixelSize.height(),
QImage::Format_RGBA8888_Premultiplied); QImage::Format_RGBA8888_Premultiplied);
if (m_rhi->isYUpInFramebuffer()) if (m_rhi->isYUpInFramebuffer())
renderImage = wrapperImage.mirrored().scaled(m_requestedSize); renderImage = wrapperImage.mirrored().scaled(m_requestedSize, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
else else
renderImage = wrapperImage.copy().scaled(m_requestedSize); renderImage = wrapperImage.copy().scaled(m_requestedSize, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}; };
QRhiResourceUpdateBatch *readbackBatch = m_rhi->nextResourceUpdateBatch(); QRhiResourceUpdateBatch *readbackBatch = m_rhi->nextResourceUpdateBatch();
readbackBatch->readBackTexture(m_texture, &readResult); readbackBatch->readBackTexture(m_texture.get(), &readResult);
QQuickRenderControlPrivate *rd = QQuickRenderControlPrivate::get(m_renderControl); QQuickRenderControlPrivate *rd = QQuickRenderControlPrivate::get(m_renderControl.get());
rd->cb->resourceUpdate(readbackBatch); rd->cb->resourceUpdate(readbackBatch);
m_renderControl->endFrame(); m_renderControl->endFrame();

View File

@@ -5,15 +5,16 @@
#include "../qmlbase.h" #include "../qmlbase.h"
#include <rhi/qrhi.h>
#include <private/qquickrendercontrol_p.h>
#include <private/qquickrendertarget_p.h>
#include <QQmlEngine>
#include <QQuickWindow>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QQuickItem; class QQuickItem;
class QQuickRenderControl;
class QQuickWindow;
class QRhi;
class QRhiRenderBuffer;
class QRhiRenderPassDescriptor;
class QRhiTexture;
class QRhiTextureRenderTarget;
QT_END_NAMESPACE QT_END_NAMESPACE
class QmlRenderer : public QmlBase class QmlRenderer : public QmlBase
@@ -42,13 +43,15 @@ private:
bool m_is3D = false; bool m_is3D = false;
bool m_fit3D = false; bool m_fit3D = false;
QQuickWindow *m_window = nullptr;
QQuickItem *m_containerItem = nullptr; QQuickItem *m_containerItem = nullptr;
QQuickRenderControl *m_renderControl = nullptr;
QRhi *m_rhi = nullptr; QRhi *m_rhi = nullptr;
QRhiTexture *m_texture = nullptr;
QRhiRenderBuffer *m_buffer = nullptr; std::unique_ptr<QQmlEngine> m_engine;
QRhiTextureRenderTarget *m_texTarget = nullptr; std::unique_ptr<QQuickRenderControl> m_renderControl;
QRhiRenderPassDescriptor *m_rpDesc = nullptr; std::unique_ptr<QQuickWindow> m_window;
std::unique_ptr<QObject> m_helper;
std::unique_ptr<QRhiTexture> m_texture;
std::unique_ptr<QRhiRenderBuffer> m_buffer;
std::unique_ptr<QRhiTextureRenderTarget> m_texTarget;
std::unique_ptr<QRhiRenderPassDescriptor> m_rpDesc;
}; };