QmlDesigner: Run QSB tool for modified shaders automatically

Raw shaders can no longer be used for ShaderEffect, but instead
a precompiled QSB must be used. To facilitate this, we now
automatically run QSB tool whenever a shader file changes.

Task-number: QDS-6472
Change-Id: Ic2886b815c7f428ac393d1d7623105a4e82053ae
Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2022-03-25 17:21:42 +02:00
parent 13a5a1f275
commit bd8537fc77
2 changed files with 123 additions and 3 deletions

View File

@@ -34,6 +34,8 @@
#include <nodeinstanceclientinterface.h> #include <nodeinstanceclientinterface.h>
#include <nodeinstanceserverinterface.h> #include <nodeinstanceserverinterface.h>
#include <utils/filepath.h>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QHash> #include <QHash>
#include <QImage> #include <QImage>
@@ -51,6 +53,10 @@ namespace ProjectExplorer {
class Target; class Target;
} }
namespace Utils {
class QtcProcess;
}
namespace QmlDesigner { namespace QmlDesigner {
class NodeInstanceServerProxy; class NodeInstanceServerProxy;
@@ -229,7 +235,8 @@ private: // functions
void updatePreviewImageForNode(const ModelNode &modelNode, const QImage &image); void updatePreviewImageForNode(const ModelNode &modelNode, const QImage &image);
void updateWatcher(const QString &path); void updateWatcher(const QString &path);
void handleShaderChanges();
void handleQsbProcessExit(Utils::QtcProcess *qsbProcess, const QString &shader);
void updateRotationBlocks(); void updateRotationBlocks();
void maybeResetOnPropertyChange(const PropertyName &name, const ModelNode &node, void maybeResetOnPropertyChange(const PropertyName &name, const ModelNode &node,
PropertyChangeFlags flags); PropertyChangeFlags flags);
@@ -278,7 +285,11 @@ private:
QFileSystemWatcher *m_fileSystemWatcher; QFileSystemWatcher *m_fileSystemWatcher;
QTimer m_resetTimer; QTimer m_resetTimer;
QTimer m_updateWatcherTimer; QTimer m_updateWatcherTimer;
QTimer m_generateQsbFilesTimer;
Utils::FilePath m_qsbPath;
QSet<QString> m_pendingUpdateDirs; QSet<QString> m_pendingUpdateDirs;
QSet<QString> m_pendingQsbTargets;
int m_remainingQsbTargets;
QTimer m_rotBlockTimer; QTimer m_rotBlockTimer;
}; };

View File

@@ -92,12 +92,18 @@
#include <hdrimage.h> #include <hdrimage.h>
#endif #endif
#include <coreplugin/messagemanager.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <qmlprojectmanager/qmlmultilanguageaspect.h> #include <qmlprojectmanager/qmlmultilanguageaspect.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <qtsupport/qtkitinformation.h>
#include <QUrl> #include <QUrl>
#include <QMultiHash> #include <QMultiHash>
@@ -161,6 +167,18 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
m_pendingUpdateDirs.clear(); m_pendingUpdateDirs.clear();
}); });
// Since generating qsb files is asynchronous and can trigger directory changes, which in turn
// can trigger qsb generation, compressing qsb generation is necessary to avoid a lot of
// unnecessary generation when project with multiple shaders is opened.
m_generateQsbFilesTimer.setSingleShot(true);
m_generateQsbFilesTimer.setInterval(100);
QObject::connect(&m_generateQsbFilesTimer, &QTimer::timeout, [this] {
handleShaderChanges();
if (m_qsbPath.isEmpty() || m_remainingQsbTargets <= 0)
m_resetTimer.start();
});
connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged,
[this](const QString &path) { [this](const QString &path) {
const QSet<QString> pendingDirs = m_pendingUpdateDirs; const QSet<QString> pendingDirs = m_pendingUpdateDirs;
@@ -177,8 +195,9 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
m_updateWatcherTimer.start(); m_updateWatcherTimer.start();
}); });
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this] { connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this](const QString &path) {
m_resetTimer.start(); m_pendingQsbTargets.insert(path);
m_generateQsbFilesTimer.start();
}); });
m_rotBlockTimer.setSingleShot(true); m_rotBlockTimer.setSingleShot(true);
@@ -1461,6 +1480,17 @@ void NodeInstanceView::setTarget(ProjectExplorer::Target *newTarget)
{ {
if (m_currentTarget != newTarget) { if (m_currentTarget != newTarget) {
m_currentTarget = newTarget; m_currentTarget = newTarget;
if (m_currentTarget && m_currentTarget->kit()) {
if (QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(m_currentTarget->kit())) {
m_qsbPath = qtVer->binPath().pathAppended("qsb").withExecutableSuffix();
if (!m_qsbPath.exists())
m_qsbPath.clear();
}
}
m_generateQsbFilesTimer.stop();
m_pendingQsbTargets.clear();
m_remainingQsbTargets = 0;
restartProcess(); restartProcess();
} }
} }
@@ -1904,9 +1934,88 @@ void NodeInstanceView::updateWatcher(const QString &path)
m_fileSystemWatcher->removePaths(oldFiles); m_fileSystemWatcher->removePaths(oldFiles);
if (!newFiles.isEmpty()) if (!newFiles.isEmpty())
m_fileSystemWatcher->addPaths(newFiles); m_fileSystemWatcher->addPaths(newFiles);
for (const auto &newFile : qAsConst(newFiles)) {
if (!oldFiles.contains(newFile))
m_pendingQsbTargets.insert(newFile);
}
if (!m_pendingQsbTargets.isEmpty())
m_generateQsbFilesTimer.start();
} }
} }
void NodeInstanceView::handleQsbProcessExit(Utils::QtcProcess *qsbProcess, const QString &shader)
{
--m_remainingQsbTargets;
QString errStr = qsbProcess->errorString();
QByteArray stdErrStr = qsbProcess->readAllStandardError();
if (!errStr.isEmpty() || !stdErrStr.isEmpty()) {
Core::MessageManager::writeSilently(
QCoreApplication::translate("QmlDesigner::NodeInstanceView",
"Failed to generate QSB file for: %1")
.arg(shader));
if (!errStr.isEmpty())
Core::MessageManager::writeSilently(errStr);
if (!stdErrStr.isEmpty())
Core::MessageManager::writeSilently(QString::fromUtf8(stdErrStr));
}
if (m_remainingQsbTargets <= 0)
m_resetTimer.start();
qsbProcess->deleteLater();
}
void NodeInstanceView::handleShaderChanges()
{
m_remainingQsbTargets += m_pendingQsbTargets.size();
for (const auto &shader : qAsConst(m_pendingQsbTargets)) {
// Run qsb for changed shader file
if (!m_qsbPath.isEmpty() && !shader.isEmpty()) {
const Utils::FilePath sourceFile = Utils::FilePath::fromString(shader);
const Utils::FilePath srcPath = sourceFile.absolutePath();
const Utils::FilePath outPath = Utils::FilePath::fromString(shader + ".qsb");
if (!sourceFile.exists() || (outPath.exists() && outPath.lastModified() > sourceFile.lastModified())) {
--m_remainingQsbTargets;
continue;
}
// Run QSB with same parameters as Qt build does
// TODO: Parameters should be configurable (QDS-6590)
const QStringList args = {"-s", "--glsl", "100 es,120,150", "--hlsl", "50", "--msl", "12",
"-o", outPath.toString(), shader};
auto qsbProcess = new Utils::QtcProcess;
qsbProcess->setWorkingDirectory(srcPath);
qsbProcess->setCommand({m_qsbPath, args});
qsbProcess->start();
if (!qsbProcess->waitForStarted()) {
handleQsbProcessExit(qsbProcess, shader);
continue;
}
if (qsbProcess->state() == QProcess::Running) {
connect(qsbProcess, &Utils::QtcProcess::finished,
[thisView = QPointer<NodeInstanceView>(this), qsbProcess, shader]() {
if (thisView)
thisView->handleQsbProcessExit(qsbProcess, shader);
else
qsbProcess->deleteLater();
});
} else {
handleQsbProcessExit(qsbProcess, shader);
}
}
}
m_pendingQsbTargets.clear();
}
void NodeInstanceView::updateRotationBlocks() void NodeInstanceView::updateRotationBlocks()
{ {
if (!model()) if (!model())