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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | using TextureStore = QHash<QString, QImage *>; |
16 | Q_GLOBAL_STATIC(TextureStore, s_textureStore); |
17 | |
18 | template<typename T> |
19 | static 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 | |
34 | void 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 | |
105 | QPointer<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 | |
147 | bool CustomMaterial::isValid() const { return scene.root != nullptr && scene.root->runtimeType == QSSGSceneDesc::Material::RuntimeType::CustomMaterial; } |
148 | |
149 | QDataStream &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 | |
190 | QDataStream &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 | |
231 | void CustomMaterial::writeQmlComponent(const QSSGSceneDesc::Material &material, QTextStream &stream) |
232 | { |
233 | QSSGQmlUtilities::writeQmlComponent(node: material, stream, outDir: QDir()); |
234 | } |
235 | |
236 | QT_END_NAMESPACE |
237 | |