Files
RedBear-OS/recipes/wip/qt/qtquick3d/source/tools/materialeditor/materialadapter.cpp
T
vasilito ff4ff35918 feat: track all source trees in git — full fork offline-first model
Red Bear OS is a full fork. All sources must be available from git clone
with zero network access. Removed gitignore rules that excluded fetched
source trees under recipes/*/source/, local/recipes/kde/*/source/,
local/recipes/qt/*/source/, and vendor source trees.

Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded.

127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt
frameworks, mesa, wayland, DRM drivers, and every other recipe source.
2026-05-14 10:55:53 +01:00

647 lines
21 KiB
C++

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "materialadapter.h"
#include <QtQuick3D/private/qquick3dshaderutils_p.h>
#include <QtQuick3D/private/qquick3dobject_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdir.h>
#include <QtCore/QDataStream>
#include <QtCore/qbuffer.h>
#include <QtQuick3DAssetUtils/private/qssgrtutilities_p.h>
#include <QtQuick3DAssetUtils/private/qssgqmlutilities_p.h>
#include <QtGui/QImageReader>
#include "uniformmodel.h"
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
namespace {
class CustomMaterialExposed : public QQuick3DCustomMaterial
{
public:
using Dirty = QQuick3DCustomMaterial::Dirty;
using QQuick3DCustomMaterial::markDirty;
CustomMaterialExposed() = delete;
};
}
enum class ShaderType
{
Vertex,
Fragment
};
static QString getScheme() { return u"q3dres"_s; }
static QString getUserType() { return u"material"_s; }
static constexpr QStringView fileSuffix(ShaderType type) { return (type == ShaderType::Vertex) ? u".vert" : u".frag"; }
static QUrl defaultShaderUrl(ShaderType type)
{
return QUrl(getScheme() + "://material@editor" + fileSuffix(type).toString());
}
using BuilderPtr = QPointer<MaterialAdapter>;
Q_GLOBAL_STATIC(BuilderPtr, builderInstance);
[[nodiscard]] static BuildMessage parseErrorMessage(const QString &errorMsg)
{
// "ERROR: :1: '' : syntax error, unexpected IDENTIFIER"
const QString head = QString::fromLatin1("ERROR:");
qint32 lineNr = -1;
qint32 columnNr = -1;
QString identifier;
auto msg = errorMsg;
if (errorMsg.startsWith(head)) {
auto pos = head.size();
auto idx = errorMsg.indexOf(u':', pos);
if (idx > pos && idx < pos + 16 /* sanity check */) {
pos = idx;
idx = errorMsg.indexOf(u':', pos + 1);
// Line nr
if (idx > pos && idx < pos + 6 /* sanity check */) {
auto mid = errorMsg.mid(pos + 1, idx - pos - 1);
bool ok = false;
auto v = mid.toInt(&ok);
if (ok) {
lineNr = v;
pos = idx;
}
// check if we have a symbol (this might be empty)
idx = errorMsg.indexOf(u'\'', pos + 1);
if (idx > pos) {
pos = idx;
idx = errorMsg.indexOf(u'\'', pos + 1);
if (idx > pos && idx > pos + 1)
identifier = errorMsg.mid(pos + 1, idx - pos - 1);
}
// Find the message
idx = errorMsg.indexOf(u':', pos + 1);
if (idx > pos)
msg = errorMsg.mid(idx + 1).trimmed();
}
}
}
return BuildMessage{ msg, identifier, lineNr, columnNr, BuildMessage::Status::Error };
}
// NOTE: We're being called from the render thread here...
void MaterialAdapter::bakerStatusCallback(const QByteArray &descKey, QtQuick3DEditorHelpers::ShaderBaker::Status status, const QString &err, QShader::Stage stage)
{
(void)descKey;
if (auto that = (*builderInstance)) {
using namespace QtQuick3DEditorHelpers::ShaderBaker;
if (status == Status::Success) {
if (stage == QShader::Stage::VertexStage) {
auto fileName = (!that->m_vertexUrl.isEmpty()) ? that->m_vertexUrl.path() : QLatin1String("<VERT_BUFFER>");
that->m_vertexMsg= { BuildMessage{}, fileName, ShaderBuildMessage::Stage::Vertex };
Q_EMIT that->vertexStatusChanged();
} else {
auto fileName = (!that->m_fragUrl.isEmpty()) ? that->m_fragUrl.path() : QLatin1String("<FRAG_BUFFER>");
that->m_fragmentMsg = { BuildMessage{}, fileName, ShaderBuildMessage::Stage::Fragment };
Q_EMIT that->fragmentStatusChanged();
}
} else if (status == Status::Error) {
const auto errList = err.split(u'\n');
if (errList.size() > 0) {
auto statusMessage = parseErrorMessage(errList.first());
if (stage == QShader::Stage::VertexStage) {
auto fileName = (!that->m_vertexUrl.isEmpty()) ? that->m_vertexUrl.path() : QLatin1String("<VERT_BUFFER>");
that->m_vertexMsg = { statusMessage, fileName, ShaderBuildMessage::Stage::Vertex };
Q_EMIT that->vertexStatusChanged();
} else {
auto fileName = (!that->m_fragUrl.isEmpty()) ? that->m_fragUrl.path() : QLatin1String("<FRAG_BUFFER>");
that->m_fragmentMsg = { statusMessage, fileName, ShaderBuildMessage::Stage::Fragment };
Q_EMIT that->fragmentStatusChanged();
}
#if 0
const auto shaderUrl = (stage == QShader::Stage::VertexStage) ? that->m_vertexUrl : that->m_fragUrl;
qDebug() << shaderUrl.host() << "=>" << that->m_error;
#endif
}
} else {
Q_UNREACHABLE();
}
}
}
// NOTE: Called from the sync phase.
static bool resolveShader(const QUrl &url, const QQmlContext *context, QByteArray &shaderData, QByteArray &shaderPathKey)
{
Q_UNUSED(context);
Q_UNUSED(shaderPathKey);
if (auto that = (*builderInstance)) {
if (url.scheme() == getScheme() && url.userInfo() == getUserType()) {
const auto filenName = url.host();
if (filenName.endsWith(fileSuffix(ShaderType::Fragment))) {
shaderData = that->fragmentShader().toUtf8();
return true;
}
if (filenName.endsWith(fileSuffix(ShaderType::Vertex))) {
shaderData = that->vertexShader().toUtf8();
return true;
}
} else {
const QUrl loadUrl = context ? context->resolvedUrl(url) : url;
const auto path = (loadUrl.scheme() == u"qrc") ? QDir::currentPath() + loadUrl.path()
: loadUrl.path();
QFile f(path);
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
shaderData = f.readAll();
if (path.endsWith(fileSuffix(ShaderType::Vertex)))
that->setVertexShader(shaderData);
else if (path.endsWith(fileSuffix(ShaderType::Fragment)))
that->setFragmentShader(shaderData);
return true;
}
}
}
return false;
}
void MaterialAdapter::updateShader(QQuick3DMaterial &target)
{
if (QQuick3DObjectPrivate::get(&target)->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
QQuick3DCustomMaterial &material = static_cast<QQuick3DCustomMaterial &>(target);
// We mark the material as dirty, this will trigger the material to reload the
// shader source file, which then again will trigger our resolveShader() function.
CustomMaterialExposed::markDirty(material, CustomMaterialExposed::Dirty::ShaderSettingsDirty);
CustomMaterialExposed::markDirty(material, CustomMaterialExposed::Dirty::DynamicPropertiesDirty);
}
}
void MaterialAdapter::updateMaterialDescription(CustomMaterial::Shaders shaders)
{
// TODO: We might need to make some more clean-up of textures and front-end nodes
// that are now replaced, but leaving as-is for now.
auto oldMaterial = m_material;
if (m_rootNode != nullptr) {
if (auto v = m_materialDescr.create(*m_rootNode, uniformTable, m_properties, shaders)) {
m_material = v;
CustomMaterialExposed::markDirty(*m_material, CustomMaterialExposed::Dirty::ShaderSettingsDirty);
CustomMaterialExposed::markDirty(*m_material, CustomMaterialExposed::Dirty::DynamicPropertiesDirty);
Q_EMIT materialChanged();
}
}
}
void MaterialAdapter::updateMaterialDescription()
{
updateMaterialDescription({ defaultShaderUrl(ShaderType::Vertex), defaultShaderUrl(ShaderType::Fragment) });
}
MaterialAdapter::MaterialAdapter(QObject *parent)
: QObject(parent)
{
// NOTE (todo?): As-is this means there can only be one ShaderAdapter per process.
Q_ASSERT((*builderInstance).isNull());
(*builderInstance) = this;
QSSGShaderUtils::setResolveFunction(&resolveShader);
QtQuick3DEditorHelpers::ShaderBaker::setStatusCallback(&bakerStatusCallback);
}
QQuick3DCustomMaterial *MaterialAdapter::material() const
{
return m_material;
}
QString MaterialAdapter::fragmentShader() const
{
return m_fragmentShader;
}
void MaterialAdapter::setFragmentShader(const QString &newFragmentShader)
{
if (m_fragmentShader == newFragmentShader)
return;
m_fragmentShader = newFragmentShader;
emit fragmentShaderChanged();
setUnsavedChanges(true);
if (m_material)
updateShader(*m_material);
}
QString MaterialAdapter::vertexShader() const
{
return m_vertexShader;
}
void MaterialAdapter::setVertexShader(const QString &newVertexShader)
{
if (m_vertexShader == newVertexShader)
return;
m_vertexShader = newVertexShader;
emit vertexShaderChanged();
setUnsavedChanges(true);
if (m_material)
updateShader(*m_material);
}
ShaderBuildMessage MaterialAdapter::vertexStatus() const
{
return m_vertexMsg;
}
ShaderBuildMessage MaterialAdapter::fragmentStatus() const
{
return m_fragmentMsg;
}
QString MaterialAdapter::importShader(const QUrl &shaderFile)
{
QString shaderContents;
QFile file = resolveFileFromUrl(shaderFile);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
shaderContents = file.readAll();
else
qWarning() << "Could not open shader file: " << file.fileName();
return shaderContents;
}
QFile MaterialAdapter::resolveFileFromUrl(const QUrl &fileUrl)
{
const QQmlContext *context = qmlContext(this);
const auto resolvedUrl = context ? context->resolvedUrl(fileUrl) : fileUrl;
const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl);
QFileInfo fileInfo(qmlSource);
QString filePath = fileInfo.canonicalFilePath();
if (filePath.isEmpty())
filePath = fileInfo.absoluteFilePath();
return QFile(filePath);
}
void MaterialAdapter::importFragmentShader(const QUrl &shaderFile)
{
setFragmentShader(importShader(shaderFile));
}
void MaterialAdapter::importVertexShader(const QUrl &shaderFile)
{
setVertexShader(importShader(shaderFile));
}
bool MaterialAdapter::save()
{
if (!m_materialSaveFile.isEmpty())
return saveMaterial(m_materialSaveFile);
return false;
}
static const quint32 MATERIAL_MAGIC = 3365961549;
static const quint32 MATERIAL_VERSION = 1;
bool MaterialAdapter::saveMaterial(const QUrl &materialFile)
{
auto saveFile = resolveFileFromUrl(materialFile);
if (saveFile.open(QIODevice::WriteOnly)) {
QDataStream out(&saveFile);
out.setByteOrder(QDataStream::LittleEndian);
out.setFloatingPointPrecision(QDataStream::SinglePrecision);
out.setVersion(QDataStream::Qt_6_3);
out << MATERIAL_MAGIC << MATERIAL_VERSION;
out << m_vertexShader;
out << m_fragmentShader;
out << int(m_material->srcBlend());
out << int(m_material->dstBlend());
out << int(m_material->cullMode());
out << int(m_material->depthDrawMode());
out << int(m_material->shadingMode());
// Uniforms
out << uniformTable.size();
for (const auto &uniform : std::as_const(uniformTable))
out << uniform;
} else {
emit errorOccurred();
return false;
}
setUnsavedChanges(false);
setMaterialSaveFile(materialFile);
emit postMaterialSaved();
return true;
}
bool MaterialAdapter::loadMaterial(const QUrl &materialFile)
{
auto loadFile = resolveFileFromUrl(materialFile);
if (loadFile.open(QIODevice::ReadOnly)) {
QDataStream in(&loadFile);
in.setByteOrder(QDataStream::LittleEndian);
in.setFloatingPointPrecision(QDataStream::SinglePrecision);
in.setVersion(QDataStream::Qt_6_3);
quint32 magic = 0;
quint32 version = 0;
in >> magic >> version;
if (magic != MATERIAL_MAGIC && version < MATERIAL_VERSION)
return false;
QString vertexShader;
QString fragmentShader;
in >> vertexShader >> fragmentShader;
setVertexShader(vertexShader);
setFragmentShader(fragmentShader);
int sourceBlend;
int destBlend;
int cullMode;
int depthDrawMode;
int shadingMode;
in >> sourceBlend >> destBlend >> cullMode >> depthDrawMode >> shadingMode;
m_material->setSrcBlend(QQuick3DCustomMaterial::BlendMode(sourceBlend));
m_material->setDstBlend(QQuick3DCustomMaterial::BlendMode(destBlend));
m_material->setCullMode(QQuick3DMaterial::CullMode(cullMode));
m_material->setDepthDrawMode(QQuick3DMaterial::DepthDrawMode(depthDrawMode));
m_material->setShadingMode(QQuick3DCustomMaterial::ShadingMode(shadingMode));
// Uniforms
qsizetype uniformsCount = 0;
in >> uniformsCount;
uniformTable.clear();
for (qsizetype i = 0; i < uniformsCount; ++i) {
CustomMaterial::Uniform uniform = { };
in >> uniform;
uniformTable.append(uniform);
}
// We have a new table, so update the table model
if (m_uniformModel) {
m_uniformModel->setModelData(&uniformTable);
updateMaterialDescription();
}
// Set filename to loaded one
setMaterialSaveFile(materialFile);
} else {
return false;
}
setUnsavedChanges(false);
return true;
}
bool MaterialAdapter::exportQmlComponent(const QUrl &componentFile, const QString &vertName, const QString &fragName)
{
QFileInfo fi(componentFile.toLocalFile());
auto filename = fi.fileName();
if (filename.isEmpty())
return false;
// Some sanity checks
// Ensure the component starts with an upper-case letter.
const auto firstLetter = filename.at(0);
if (!firstLetter.isLetter()) {
qWarning() << "Component name needs to start with an upper-case letter!";
return false;
}
// Assume this is what the user wanted and fix it now.
if (firstLetter.isLower()) {
qWarning() << "Component name needs to start with an upper-case letter!";
filename[0] = firstLetter.toUpper();
}
static const auto saveShader = [](const QDir &dir, const QString &filename, const QString &text) {
const QString savePath = dir.path() + QDir::separator() + filename;
auto saveFile = QFile(savePath);
bool ret = false;
if (saveFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
QTextStream out(&saveFile);
out << text;
ret = true;
} else {
qWarning("Unable to open \'%s\' for writing", qPrintable(savePath));
}
return ret;
};
static const auto relShaderUrl = [](const QString &name, ShaderType type) {
QString relPath;
if (name.size() > 0) {
auto suffix = fileSuffix(type);
if (!name.endsWith(suffix))
relPath = name + suffix.toString();
else
relPath = name;
}
return QUrl(relPath);
};
bool ret = false;
const auto &dir = fi.dir();
auto dirPath = dir.path();
if (!dirPath.isEmpty()) {
if (m_materialDescr.isValid()) {
// NOTE: Relative paths. The shaders are exported with the component and we assume they live in the same location.
CustomMaterial::Shaders shaders = { relShaderUrl(vertName, ShaderType::Vertex), relShaderUrl(fragName, ShaderType::Fragment) };
const bool vertShaderOk = (m_vertexShader.size() > 0) ? saveShader(dir, shaders.vert.fileName(), m_vertexShader) : true;
const bool fragShaderOk = (m_fragmentShader.size() > 0) ? saveShader(dir, shaders.frag.fileName(), m_fragmentShader) : true;
if (vertShaderOk && fragShaderOk) {
updateMaterialDescription(shaders);
auto saveFile = QFile(dirPath + QDir::separator() + filename);
if (saveFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
const auto orgPath = QDir::current().path();
QDir::setCurrent(dirPath);
QTextStream out(&saveFile);
out << m_materialDescr;
QDir::setCurrent(orgPath);
ret = true;
}
} else {
emit errorOccurred();
ret = false;
}
// Re-set the shader urls
updateMaterialDescription();
}
}
return ret;
}
void MaterialAdapter::reset()
{
m_properties = CustomMaterial::Properties{};
if (m_material == nullptr)
return;
delete m_material;
uniformTable = {};
if (m_uniformModel)
m_uniformModel->setModelData(&uniformTable);
// Set Default Shader Templates
setFragmentShader(QString());
setVertexShader(QString());
updateMaterialDescription();
}
UniformModel *MaterialAdapter::uniformModel() const
{
return m_uniformModel;
}
bool MaterialAdapter::unsavedChanges() const
{
return m_unsavedChanges;
}
void MaterialAdapter::setUnsavedChanges(bool newUnsavedChanges)
{
if (m_unsavedChanges == newUnsavedChanges)
return;
m_unsavedChanges = newUnsavedChanges;
emit unsavedChangesChanged();
}
const QUrl &MaterialAdapter::materialSaveFile() const
{
return m_materialSaveFile;
}
void MaterialAdapter::setMaterialSaveFile(const QUrl &newMaterialSaveFile)
{
if (m_materialSaveFile == newMaterialSaveFile)
return;
m_materialSaveFile = newMaterialSaveFile;
emit materialSaveFileChanged();
}
QQuick3DNode *MaterialAdapter::rootNode() const
{
return m_rootNode;
}
void MaterialAdapter::setRootNode(QQuick3DNode *newResourceNode)
{
if (m_rootNode == newResourceNode)
return;
m_rootNode = newResourceNode;
emit rootNodeChanged();
updateMaterialDescription();
}
MaterialAdapter::CullMode MaterialAdapter::cullMode() const
{
return m_properties.cullMode;
}
void MaterialAdapter::setCullMode(CullMode newCullMode)
{
if (m_properties.cullMode == newCullMode)
return;
m_properties.cullMode = newCullMode;
emit cullModeChanged();
updateMaterialDescription();
}
MaterialAdapter::DepthDrawMode MaterialAdapter::depthDrawMode() const
{
return m_properties.depthDrawMode;
}
void MaterialAdapter::setDepthDrawMode(DepthDrawMode newDepthDrawMode)
{
if (m_properties.depthDrawMode == newDepthDrawMode)
return;
m_properties.depthDrawMode = newDepthDrawMode;
emit depthDrawModeChanged();
updateMaterialDescription();
}
MaterialAdapter::ShadingMode MaterialAdapter::shadingMode() const
{
return m_properties.shadingMode;
}
void MaterialAdapter::setShadingMode(ShadingMode newShadingMode)
{
if (m_properties.shadingMode == newShadingMode)
return;
m_properties.shadingMode = newShadingMode;
emit shadingModeChanged();
updateMaterialDescription();
}
MaterialAdapter::BlendMode MaterialAdapter::srcBlend() const
{
return m_properties.sourceBlend;
}
void MaterialAdapter::setSrcBlend(BlendMode newSourceBlend)
{
if (m_properties.sourceBlend == newSourceBlend)
return;
m_properties.sourceBlend = newSourceBlend;
emit srcBlendChanged();
updateMaterialDescription();
}
MaterialAdapter::BlendMode MaterialAdapter::dstBlend() const
{
return m_properties.destinationBlend;
}
void MaterialAdapter::setDstBlend(BlendMode newDestinationBlend)
{
if (m_properties.destinationBlend == newDestinationBlend)
return;
m_properties.destinationBlend = newDestinationBlend;
emit dstBlendChanged();
updateMaterialDescription();
}
void MaterialAdapter::setUniformModel(UniformModel *newUniformModel)
{
m_uniformModel = newUniformModel;
if (m_uniformModel) {
m_uniformModel->setModelData(&uniformTable);
connect(m_uniformModel, &UniformModel::dataChanged, this, [this]() {
updateMaterialDescription();
});
}
emit uniformModelChanged();
}
QString MaterialAdapter::getSupportedImageFormatsFilter() const
{
auto formats = QImageReader::supportedImageFormats();
QString imageFilter = QStringLiteral("Image files (");
for (const auto &format : std::as_const(formats))
imageFilter += QStringLiteral("*.") + format + QStringLiteral(" ");
imageFilter += QStringLiteral(")");
return imageFilter;
}
QT_END_NAMESPACE