QmlDesigner: Render effects on document level

Rendering effects requires actually rendering another item.
The effect is defined by another item that is rendered instead
of the original item.

For items that have an effect we do not render the children, since
the effect is only applied to the layer not the individual items.

We set layer.enabled temporarily to false, this ensures the effect
is not rendered as part of its parent item.

To detect the effect we use the source property an check if it points
to the ShaderEffectSource.

If one of the children inside the effect is transformed we have
to update the effect item.

If layer.enabled or layer.effect is changed we set the dirty flag
on the item and all children.

Change-Id: Iff61ef950e62a7a598b4bfa181ea70cb144368f3
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Thomas Hartmann
2021-08-26 15:47:56 +02:00
parent 3945868670
commit 870b619c85
6 changed files with 124 additions and 15 deletions

View File

@@ -335,6 +335,44 @@ QImage Qt5NodeInstanceServer::grabWindow()
return {}; return {};
} }
static bool hasEffect(QQuickItem *item)
{
QQuickItemPrivate *pItem = QQuickItemPrivate::get(item);
return pItem && pItem->layer() && pItem->layer()->enabled() && pItem->layer()->effect();
}
QQuickItem *Qt5NodeInstanceServer::parentEffectItem(QQuickItem *item)
{
QQuickItem *parent = item->parentItem();
while (parent) {
if (hasEffect(parent))
return parent;
parent = parent->parentItem();
}
return nullptr;
}
static bool isEffectItem(QQuickItem *item, QQuickShaderEffectSource *sourceItem)
{
QQuickItemPrivate *pItem = QQuickItemPrivate::get(sourceItem);
if (!pItem || !pItem->layer())
return false;
const auto propName = pItem->layer()->name();
QQmlProperty prop(item, QString::fromLatin1(propName));
if (!prop.isValid())
return false;
return prop.read().value<QQuickShaderEffectSource *>() == sourceItem;
}
static bool isLayerEnabled(QQuickItemPrivate *item)
{
return item && item->layer() && item->layer()->enabled();
}
QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item) QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
{ {
QImage renderImage; QImage renderImage;
@@ -343,10 +381,29 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
return {}; return {};
QQuickItemPrivate *pItem = QQuickItemPrivate::get(item); QQuickItemPrivate *pItem = QQuickItemPrivate::get(item);
pItem->refFromEffectItem(false);
const bool renderEffects = qEnvironmentVariableIsSet("QMLPUPPET_RENDER_EFFECTS");
if (renderEffects) {
if (parentEffectItem(item))
return renderImage;
// Effects are actually implemented as a separate item we have to find first
if (hasEffect(item)) {
if (auto parent = item->parentItem()) {
const auto siblings = parent->childItems();
for (auto sibling : siblings) {
if (isEffectItem(sibling, pItem->layer()->effectSource()))
return grabItem(sibling);
}
}
}
}
if (!isLayerEnabled(pItem))
pItem->refFromEffectItem(false);
ServerNodeInstance instance = instanceForObject(item); ServerNodeInstance instance = instanceForObject(item);
const auto childInstances = instance.childItems();
// Setting layer enabled to false messes up the bounding rect. // Setting layer enabled to false messes up the bounding rect.
// Therefore we calculate it upfront. // Therefore we calculate it upfront.
@@ -358,11 +415,20 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
// Hide immediate children that have instances and are QQuickItems so we get only // Hide immediate children that have instances and are QQuickItems so we get only
// the parent item's content, as compositing is handled on creator side. // the parent item's content, as compositing is handled on creator side.
for (const auto &childInstance : childInstances) { QSet<QQuickItem *> layerChildren;
QQuickItem *childItem = qobject_cast<QQuickItem *>(childInstance.internalObject());
if (childItem) { if (instance.isValid()) { //Not valid for effect
QQuickItemPrivate *pChild = QQuickItemPrivate::get(childItem); const auto childInstances = instance.childItems();
pChild->refFromEffectItem(true); for (const auto &childInstance : childInstances) {
QQuickItem *childItem = qobject_cast<QQuickItem *>(childInstance.internalObject());
if (childItem) {
QQuickItemPrivate *pChild = QQuickItemPrivate::get(childItem);
if (pChild->layer() && pChild->layer()->enabled()) {
layerChildren.insert(childItem);
pChild->layer()->setEnabled(false);
}
pChild->refFromEffectItem(true);
}
} }
} }
@@ -406,15 +472,24 @@ QImage Qt5NodeInstanceServer::grabItem(QQuickItem *item)
m_viewData.renderControl->endFrame(); m_viewData.renderControl->endFrame();
// Restore visibility of immediate children that have instances and are QQuickItems if (instance.isValid()) { //Not valid for effect
for (const auto &childInstance : childInstances) { const auto childInstances = instance.childItems();
QQuickItem *childItem = qobject_cast<QQuickItem *>(childInstance.internalObject());
if (childItem) { // Restore visibility of immediate children that have instances and are QQuickItems
QQuickItemPrivate *pChild = QQuickItemPrivate::get(childItem); for (const auto &childInstance : childInstances) {
pChild->derefFromEffectItem(true); QQuickItem *childItem = qobject_cast<QQuickItem *>(childInstance.internalObject());
if (childItem) {
QQuickItemPrivate *pChild = QQuickItemPrivate::get(childItem);
pChild->derefFromEffectItem(true);
if (pChild->layer() && layerChildren.contains(childItem))
pChild->layer()->setEnabled(true);
}
} }
} }
pItem->derefFromEffectItem(false);
if (!isLayerEnabled(pItem))
pItem->derefFromEffectItem(false);
#else #else
Q_UNUSED(item) Q_UNUSED(item)
#endif #endif

View File

@@ -71,6 +71,8 @@ public:
QImage grabWindow() override; QImage grabWindow() override;
QImage grabItem(QQuickItem *item) override; QImage grabItem(QQuickItem *item) override;
static QQuickItem *parentEffectItem(QQuickItem *item);
protected: protected:
void initializeView() override; void initializeView() override;
void resizeCanvasToRootItem() override; void resizeCanvasToRootItem() override;

View File

@@ -89,8 +89,20 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
if (hasInstanceForObject(item)) { if (hasInstanceForObject(item)) {
if (DesignerSupport::isDirty(item, DesignerSupport::ContentUpdateMask)) if (DesignerSupport::isDirty(item, DesignerSupport::ContentUpdateMask))
m_dirtyInstanceSet.insert(instanceForObject(item)); m_dirtyInstanceSet.insert(instanceForObject(item));
if (QQuickItem *effectParent = parentEffectItem(item)) {
if ((DesignerSupport::isDirty(
item,
DesignerSupport::DirtyType(
DesignerSupport::TransformUpdateMask
| DesignerSupport::Visible
| DesignerSupport::ContentUpdateMask)))
&& hasInstanceForObject(effectParent)) {
m_dirtyInstanceSet.insert(instanceForObject(effectParent));
}
}
} else if (DesignerSupport::isDirty(item, DesignerSupport::AllMask)) { } else if (DesignerSupport::isDirty(item, DesignerSupport::AllMask)) {
ServerNodeInstance ancestorInstance = findNodeInstanceForItem(item->parentItem()); ServerNodeInstance ancestorInstance = findNodeInstanceForItem(
item->parentItem());
if (ancestorInstance.isValid()) if (ancestorInstance.isValid())
m_dirtyInstanceSet.insert(ancestorInstance); m_dirtyInstanceSet.insert(ancestorInstance);
} }

View File

@@ -612,6 +612,18 @@ void QuickItemNodeInstance::updateAllDirtyNodesRecursive(QQuickItem *parentItem)
updateDirtyNode(parentItem); updateDirtyNode(parentItem);
} }
void QuickItemNodeInstance::setAllNodesDirtyRecursive(QQuickItem *parentItem) const
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
Q_UNUSED(parentItem)
#else
const QList<QQuickItem *> children = parentItem->childItems();
for (QQuickItem *childItem : children)
setAllNodesDirtyRecursive(childItem);
DesignerSupport::addDirty(parentItem, QQuickDesignerSupport::Content);
#endif
}
static inline bool isRectangleSane(const QRectF &rect) static inline bool isRectangleSane(const QRectF &rect)
{ {
return rect.isValid() && (rect.width() < 10000) && (rect.height() < 10000); return rect.isValid() && (rect.width() < 10000) && (rect.height() < 10000);
@@ -813,6 +825,9 @@ void QuickItemNodeInstance::setPropertyVariant(const PropertyName &name, const Q
if (name == "y") if (name == "y")
m_y = value.toDouble(); m_y = value.toDouble();
if (name == "layer.enabled" || name == "layer.effect")
setAllNodesDirtyRecursive(quickItem());
ObjectNodeInstance::setPropertyVariant(name, value); ObjectNodeInstance::setPropertyVariant(name, value);
refresh(); refresh();
@@ -882,6 +897,9 @@ void QuickItemNodeInstance::resetProperty(const PropertyName &name)
if (name == "y") if (name == "y")
m_y = 0.0; m_y = 0.0;
if (name == "layer.enabled" || name == "layer.effect")
setAllNodesDirtyRecursive(quickItem());
DesignerSupport::resetAnchor(quickItem(), QString::fromUtf8(name)); DesignerSupport::resetAnchor(quickItem(), QString::fromUtf8(name));
if (name == "anchors.fill") { if (name == "anchors.fill") {

View File

@@ -118,6 +118,7 @@ protected:
Qt5NodeInstanceServer *qt5NodeInstanceServer() const; Qt5NodeInstanceServer *qt5NodeInstanceServer() const;
void updateDirtyNodesRecursive(QQuickItem *parentItem) const; void updateDirtyNodesRecursive(QQuickItem *parentItem) const;
void updateAllDirtyNodesRecursive(QQuickItem *parentItem) const; void updateAllDirtyNodesRecursive(QQuickItem *parentItem) const;
void setAllNodesDirtyRecursive(QQuickItem *parentItem) const;
QRectF boundingRectWithStepChilds(QQuickItem *parentItem) const; QRectF boundingRectWithStepChilds(QQuickItem *parentItem) const;
void resetHorizontal(); void resetHorizontal();
void resetVertical(); void resetVertical();

View File

@@ -493,6 +493,7 @@ QProcessEnvironment PuppetCreator::processEnvironment() const
environment.set("QML_BAD_GUI_RENDER_LOOP", "true"); environment.set("QML_BAD_GUI_RENDER_LOOP", "true");
environment.set("QML_PUPPET_MODE", "true"); environment.set("QML_PUPPET_MODE", "true");
environment.set("QML_DISABLE_DISK_CACHE", "true"); environment.set("QML_DISABLE_DISK_CACHE", "true");
environment.set("QMLPUPPET_RENDER_EFFECTS", "true");
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (!environment.hasKey("QT_SCREEN_SCALE_FACTORS") && !environment.hasKey("QT_SCALE_FACTOR") if (!environment.hasKey("QT_SCREEN_SCALE_FACTORS") && !environment.hasKey("QT_SCALE_FACTOR")
&& QApplication::testAttribute(Qt::AA_EnableHighDpiScaling)) && QApplication::testAttribute(Qt::AA_EnableHighDpiScaling))