// 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 "custommaterial.h" #include #include #include #include QT_BEGIN_NAMESPACE using TextureStore = QHash; Q_GLOBAL_STATIC(TextureStore, s_textureStore); template static void setProperty(QQuick3DObject &obj, const char *name, T v) { if (QQuick3DObjectPrivate::get(&obj)->type == QQuick3DObjectPrivate::Type::CustomMaterial) { if constexpr (std::is_same_v) { if (QQuick3DTexture *texture = qobject_cast(v ? v->obj : nullptr)) { auto textureInput = new QQuick3DShaderUtilsTextureInput(&obj); textureInput->setTexture(texture); obj.setProperty(name, QVariant::fromValue(textureInput)); } } else { obj.setProperty(name, QVariant::fromValue(v)); } } } void CustomMaterial::setUniform(QSSGSceneDesc::Material &material, const Uniform &uniform) { if (uniform.name.isEmpty()) { qWarning() << "Uniform without name! Skipping"; return; } constexpr auto Dynamic = QSSGSceneDesc::Property::Type::Dynamic; switch (uniform.type) { case Uniform::Type::Bool: QSSGSceneDesc::setProperty(material, uniform.name.constData(), &setProperty, uniform.b, Dynamic); break; case Uniform::Type::Int: QSSGSceneDesc::setProperty(material, uniform.name.constData(), &setProperty, uniform.i, Dynamic); break; case Uniform::Type::Float: QSSGSceneDesc::setProperty(material, uniform.name.constData(), &setProperty, uniform.f, Dynamic); break; case Uniform::Type::Vec2: QSSGSceneDesc::setProperty(material, uniform.name.constData(), &setProperty, uniform.vec2, Dynamic); break; case Uniform::Type::Vec3: QSSGSceneDesc::setProperty(material, uniform.name.constData(), &setProperty, uniform.vec3, Dynamic); break; case Uniform::Type::Vec4: QSSGSceneDesc::setProperty(material, uniform.name.constData(), &setProperty, uniform.vec4, Dynamic); break; case Uniform::Type::Mat44: QSSGSceneDesc::setProperty(material, uniform.name.constData(), &setProperty, uniform.m44, Dynamic); break; case Uniform::Type::Sampler: { QFileInfo fi(uniform.imagePath); const auto &path = fi.canonicalFilePath(); if (!path.isEmpty()) { auto it = s_textureStore->constFind(path); const auto end = s_textureStore->constEnd(); if (it == end) { QImage image; if (image.load(path)) it = s_textureStore->insert(path, new QImage(image)); else qWarning("Failed to load image %s", qPrintable(path)); } if (it != end) { const auto &image = *(*it); const QSize resSize = image.size(); // Note: the image must not be deleted or modified while the textureData node is in the scene auto *imageData = reinterpret_cast(image.constBits()); QByteArray dataref = QByteArray::fromRawData(imageData, image.sizeInBytes()); const auto format = QByteArrayLiteral("rgba8888"); const auto &baseName = QString(fi.baseName() + QString::number(material.id)); auto name = baseName.toUtf8(); auto textureData = new QSSGSceneDesc::TextureData(dataref, resSize, format, 0, name); QSSGSceneDesc::addNode(material, *textureData); auto texture = new QSSGSceneDesc::Texture(QSSGSceneDesc::Texture::RuntimeType::Image2D); QSSGSceneDesc::addNode(material, *texture); QSSGSceneDesc::setProperty(*texture, "textureData", &QQuick3DTexture::setTextureData, textureData); QSSGSceneDesc::setProperty(material, uniform.name.constData(), &setProperty, texture, Dynamic); } } break; } case Uniform::Type::Last: Q_UNREACHABLE(); break; } } QPointer CustomMaterial::create(QQuick3DNode &parent, const UniformTable &uniforms, const Properties &properties, const Shaders &shaders) { using namespace QSSGSceneDesc; QQuick3DCustomMaterial *ret = nullptr; if (scene.root) scene.reset(); QSSGSceneDesc::Material *material = new Material(Material::RuntimeType::CustomMaterial); addNode(scene, *material); // Set uniforms for (const auto &uniform : uniforms) setUniform(*material, uniform); // Set common properties const Properties def; if (def.cullMode != properties.cullMode) setProperty(*material, "cullMode", &QQuick3DCustomMaterial::setCullMode, properties.cullMode); if (def.depthDrawMode != properties.depthDrawMode) setProperty(*material, "depthDrawMode", &QQuick3DCustomMaterial::setDepthDrawMode, properties.depthDrawMode); if (def.shadingMode != properties.shadingMode) setProperty(*material, "shadingMode", &QQuick3DCustomMaterial::setShadingMode, properties.shadingMode); if (def.sourceBlend != properties.sourceBlend) setProperty(*material, "sourceBlend", &QQuick3DCustomMaterial::setSrcBlend, properties.sourceBlend); if (def.destinationBlend != properties.destinationBlend) setProperty(*material, "destinationBlend", &QQuick3DCustomMaterial::setDstBlend, properties.destinationBlend); if (!shaders.vert.isEmpty()) setProperty(*material, "vertexShader", &QQuick3DCustomMaterial::setVertexShader, QUrl{ shaders.vert.toString() }); if (!shaders.frag.isEmpty()) setProperty(*material, "fragmentShader", &QQuick3DCustomMaterial::setFragmentShader, QUrl{ shaders.frag.toString() }); auto resourceParent = std::make_unique(); QSSGRuntimeUtils::createScene(parent, scene); if (auto customMaterial = qobject_cast(material->obj)) { resourceParent.release()->setParent(&parent); ret = customMaterial; } return ret; } bool CustomMaterial::isValid() const { return scene.root != nullptr && scene.root->runtimeType == QSSGSceneDesc::Material::RuntimeType::CustomMaterial; } QDataStream &CustomMaterial::readFromDataStream(QDataStream &stream, Uniform &uniform) { int type; stream >> type >> uniform.name; uniform.type = Uniform::Type(type); switch (uniform.type) { case Uniform::Type::Bool: stream >> uniform.b; break; case Uniform::Type::Int: stream >> uniform.i; break; case Uniform::Type::Float: stream >> uniform.f; break; case Uniform::Type::Vec2: stream >> uniform.vec2; break; case Uniform::Type::Vec3: stream >> uniform.vec3; break; case Uniform::Type::Vec4: stream >> uniform.vec4; break; case Uniform::Type::Mat44: stream >> uniform.m44; break; case Uniform::Type::Sampler: { QImage image; stream >> uniform.imagePath >> image; s_textureStore->insert(uniform.imagePath, new QImage(image)); } break; case Uniform::Type::Last: Q_UNREACHABLE(); break; } return stream; } QDataStream &CustomMaterial::writeToDataStream(QDataStream &stream, const Uniform &uniform) { stream << int(uniform.type) << uniform.name; switch (uniform.type) { case Uniform::Type::Bool: stream << uniform.b; break; case Uniform::Type::Int: stream << uniform.i; break; case Uniform::Type::Float: stream << uniform.f; break; case Uniform::Type::Vec2: stream << uniform.vec2; break; case Uniform::Type::Vec3: stream << uniform.vec3; break; case Uniform::Type::Vec4: stream << uniform.vec4; break; case Uniform::Type::Mat44: stream << uniform.m44; break; case Uniform::Type::Sampler: { const auto &path = uniform.imagePath; auto it = s_textureStore->constFind(path); const auto end = s_textureStore->cend(); if (it != end) stream << path << *(*it); } break; case Uniform::Type::Last: Q_UNREACHABLE(); break; } return stream; } void CustomMaterial::writeQmlComponent(const QSSGSceneDesc::Material &material, QTextStream &stream) { QSSGQmlUtilities::writeQmlComponent(material, stream, QDir()); } QT_END_NAMESPACE