1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "custommaterial.h"
5
6#include <QtCore/qdir.h>
7
8#include <QtQuick3DAssetUtils/private/qssgrtutilities_p.h>
9#include <QtQuick3DAssetUtils/private/qssgqmlutilities_p.h>
10
11#include <QtQuick3D/private/qquick3dshaderutils_p.h>
12
13QT_BEGIN_NAMESPACE
14
15using TextureStore = QHash<QString, QImage *>;
16Q_GLOBAL_STATIC(TextureStore, s_textureStore);
17
18template<typename T>
19static void setProperty(QQuick3DObject &obj, const char *name, T v)
20{
21 if (QQuick3DObjectPrivate::get(item: &obj)->type == QQuick3DObjectPrivate::Type::CustomMaterial) {
22 if constexpr (std::is_same_v<T, QSSGSceneDesc::Texture *>) {
23 if (QQuick3DTexture *texture = qobject_cast<QQuick3DTexture *>(v ? v->obj : nullptr)) {
24 auto textureInput = new QQuick3DShaderUtilsTextureInput(&obj);
25 textureInput->setTexture(texture);
26 obj.setProperty(name, value: QVariant::fromValue(value: textureInput));
27 }
28 } else {
29 obj.setProperty(name, QVariant::fromValue(v));
30 }
31 }
32}
33
34void CustomMaterial::setUniform(QSSGSceneDesc::Material &material, const Uniform &uniform)
35{
36 if (uniform.name.isEmpty()) {
37 qWarning() << "Uniform without name! Skipping";
38 return;
39 }
40
41 constexpr auto Dynamic = QSSGSceneDesc::Property::Type::Dynamic;
42
43 switch (uniform.type) {
44 case Uniform::Type::Bool:
45 QSSGSceneDesc::setProperty(node&: material, name: uniform.name.constData(), setter: &setProperty<bool>, value: uniform.b, type: Dynamic);
46 break;
47 case Uniform::Type::Int:
48 QSSGSceneDesc::setProperty(node&: material, name: uniform.name.constData(), setter: &setProperty<int>, value: uniform.i, type: Dynamic);
49 break;
50 case Uniform::Type::Float:
51 QSSGSceneDesc::setProperty(node&: material, name: uniform.name.constData(), setter: &setProperty<float>, value: uniform.f, type: Dynamic);
52 break;
53 case Uniform::Type::Vec2:
54 QSSGSceneDesc::setProperty(node&: material, name: uniform.name.constData(), setter: &setProperty<QVector2D>, value: uniform.vec2, type: Dynamic);
55 break;
56 case Uniform::Type::Vec3:
57 QSSGSceneDesc::setProperty(node&: material, name: uniform.name.constData(), setter: &setProperty<QVector3D>, value: uniform.vec3, type: Dynamic);
58 break;
59 case Uniform::Type::Vec4:
60 QSSGSceneDesc::setProperty(node&: material, name: uniform.name.constData(), setter: &setProperty<QVector4D>, value: uniform.vec4, type: Dynamic);
61 break;
62 case Uniform::Type::Mat44:
63 QSSGSceneDesc::setProperty(node&: material, name: uniform.name.constData(), setter: &setProperty<QMatrix4x4>, value: uniform.m44, type: Dynamic);
64 break;
65 case Uniform::Type::Sampler:
66 {
67 QFileInfo fi(uniform.imagePath);
68 const auto &path = fi.canonicalFilePath();
69 if (!path.isEmpty()) {
70 auto it = s_textureStore->constFind(key: path);
71 const auto end = s_textureStore->constEnd();
72 if (it == end) {
73 QImage image;
74 if (image.load(fileName: path))
75 it = s_textureStore->insert(key: path, value: new QImage(image));
76 else
77 qWarning(msg: "Failed to load image %s", qPrintable(path));
78 }
79
80 if (it != end) {
81 const auto &image = *(*it);
82 const QSize resSize = image.size();
83 // Note: the image must not be deleted or modified while the textureData node is in the scene
84 auto *imageData = reinterpret_cast<const char *>(image.constBits());
85 QByteArray dataref = QByteArray::fromRawData(data: imageData, size: image.sizeInBytes());
86 const auto format = QByteArrayLiteral("rgba8888");
87 const auto &baseName = QString(fi.baseName() + QString::number(material.id));
88 auto name = baseName.toUtf8();
89 auto textureData = new QSSGSceneDesc::TextureData(dataref, resSize, format, 0, name);
90 QSSGSceneDesc::addNode(parent&: material, node&: *textureData);
91 auto texture = new QSSGSceneDesc::Texture(QSSGSceneDesc::Texture::RuntimeType::Image2D);
92 QSSGSceneDesc::addNode(parent&: material, node&: *texture);
93 QSSGSceneDesc::setProperty(node&: *texture, name: "textureData", setter: &QQuick3DTexture::setTextureData, value: textureData);
94 QSSGSceneDesc::setProperty(node&: material, name: uniform.name.constData(), setter: &setProperty<QSSGSceneDesc::Texture *>, value&: texture, type: Dynamic);
95 }
96 }
97 break;
98 }
99 case Uniform::Type::Last:
100 Q_UNREACHABLE();
101 break;
102 }
103}
104
105QPointer<QQuick3DCustomMaterial> CustomMaterial::create(QQuick3DNode &parent, const UniformTable &uniforms, const Properties &properties, const Shaders &shaders)
106{
107 using namespace QSSGSceneDesc;
108 QQuick3DCustomMaterial *ret = nullptr;
109 if (scene.root)
110 scene.reset();
111
112 QSSGSceneDesc::Material *material = new Material(Material::RuntimeType::CustomMaterial);
113 addNode(scene, node&: *material);
114
115 // Set uniforms
116 for (const auto &uniform : uniforms)
117 setUniform(material&: *material, uniform);
118
119 // Set common properties
120 const Properties def;
121 if (def.cullMode != properties.cullMode)
122 setProperty(node&: *material, name: "cullMode", setter: &QQuick3DCustomMaterial::setCullMode, value: properties.cullMode);
123 if (def.depthDrawMode != properties.depthDrawMode)
124 setProperty(node&: *material, name: "depthDrawMode", setter: &QQuick3DCustomMaterial::setDepthDrawMode, value: properties.depthDrawMode);
125 if (def.shadingMode != properties.shadingMode)
126 setProperty(node&: *material, name: "shadingMode", setter: &QQuick3DCustomMaterial::setShadingMode, value: properties.shadingMode);
127 if (def.sourceBlend != properties.sourceBlend)
128 setProperty(node&: *material, name: "sourceBlend", setter: &QQuick3DCustomMaterial::setSrcBlend, value: properties.sourceBlend);
129 if (def.destinationBlend != properties.destinationBlend)
130 setProperty(node&: *material, name: "destinationBlend", setter: &QQuick3DCustomMaterial::setDstBlend, value: properties.destinationBlend);
131
132 if (!shaders.vert.isEmpty())
133 setProperty(node&: *material, name: "vertexShader", setter: &QQuick3DCustomMaterial::setVertexShader, value: QUrl{ shaders.vert.toString() });
134 if (!shaders.frag.isEmpty())
135 setProperty(node&: *material, name: "fragmentShader", setter: &QQuick3DCustomMaterial::setFragmentShader, value: QUrl{ shaders.frag.toString() });
136
137 auto resourceParent = std::make_unique<QQuick3DNode>();
138 QSSGRuntimeUtils::createScene(parent, scene);
139 if (auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: material->obj)) {
140 resourceParent.release()->setParent(&parent);
141 ret = customMaterial;
142 }
143
144 return ret;
145}
146
147bool CustomMaterial::isValid() const { return scene.root != nullptr && scene.root->runtimeType == QSSGSceneDesc::Material::RuntimeType::CustomMaterial; }
148
149QDataStream &CustomMaterial::readFromDataStream(QDataStream &stream, Uniform &uniform)
150{
151 int type;
152 stream >> type >> uniform.name;
153 uniform.type = Uniform::Type(type);
154 switch (uniform.type) {
155 case Uniform::Type::Bool:
156 stream >> uniform.b;
157 break;
158 case Uniform::Type::Int:
159 stream >> uniform.i;
160 break;
161 case Uniform::Type::Float:
162 stream >> uniform.f;
163 break;
164 case Uniform::Type::Vec2:
165 stream >> uniform.vec2;
166 break;
167 case Uniform::Type::Vec3:
168 stream >> uniform.vec3;
169 break;
170 case Uniform::Type::Vec4:
171 stream >> uniform.vec4;
172 break;
173 case Uniform::Type::Mat44:
174 stream >> uniform.m44;
175 break;
176 case Uniform::Type::Sampler:
177 {
178 QImage image;
179 stream >> uniform.imagePath >> image;
180 s_textureStore->insert(key: uniform.imagePath, value: new QImage(image));
181 }
182 break;
183 case Uniform::Type::Last:
184 Q_UNREACHABLE();
185 break;
186 }
187 return stream;
188}
189
190QDataStream &CustomMaterial::writeToDataStream(QDataStream &stream, const Uniform &uniform)
191{
192 stream << int(uniform.type) << uniform.name;
193 switch (uniform.type) {
194 case Uniform::Type::Bool:
195 stream << uniform.b;
196 break;
197 case Uniform::Type::Int:
198 stream << uniform.i;
199 break;
200 case Uniform::Type::Float:
201 stream << uniform.f;
202 break;
203 case Uniform::Type::Vec2:
204 stream << uniform.vec2;
205 break;
206 case Uniform::Type::Vec3:
207 stream << uniform.vec3;
208 break;
209 case Uniform::Type::Vec4:
210 stream << uniform.vec4;
211 break;
212 case Uniform::Type::Mat44:
213 stream << uniform.m44;
214 break;
215 case Uniform::Type::Sampler:
216 {
217 const auto &path = uniform.imagePath;
218 auto it = s_textureStore->constFind(key: path);
219 const auto end = s_textureStore->cend();
220 if (it != end)
221 stream << path << *(*it);
222 }
223 break;
224 case Uniform::Type::Last:
225 Q_UNREACHABLE();
226 break;
227 }
228 return stream;
229}
230
231void CustomMaterial::writeQmlComponent(const QSSGSceneDesc::Material &material, QTextStream &stream)
232{
233 QSSGQmlUtilities::writeQmlComponent(node: material, stream, outDir: QDir());
234}
235
236QT_END_NAMESPACE
237

source code of qtquick3d/tools/materialeditor/custommaterial.cpp