1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "material.h"
16
17 #include <mutex>
18 #include <scene/ext/util.h>
19 #include <set>
20
21 #include <3d/ecs/components/name_component.h>
22 #include <core/log.h>
23
24 #include <meta/api/engine/util.h>
25 #include <meta/api/make_callback.h>
26 #include <meta/interface/engine/intf_engine_value_manager.h>
27
28 #include "entity_converting_value.h"
29 #include "mesh/texture.h"
30 #include "util_interfaces.h"
31
32 SCENE_BEGIN_NAMESPACE()
33 namespace {
34 struct ShaderConverter {
ShaderConverter__anon1fbf93410111::ShaderConverter35 ShaderConverter(const IInternalScene::Ptr& scene) : scene_(scene) {}
36
37 using SourceType = IShader::Ptr;
38 using TargetType = CORE3D_NS::MaterialComponent::Shader;
39
ConvertToSource__anon1fbf93410111::ShaderConverter40 SourceType ConvertToSource(META_NS::IAny& any, const TargetType& v) const
41 {
42 auto p = META_NS::GetPointer<IShader>(any);
43 if (auto scene = scene_.lock()) {
44 scene
45 ->AddTask([&] {
46 if (auto rhman = static_cast<CORE3D_NS::IRenderHandleComponentManager*>(
47 scene->GetEcsContext().FindComponent<CORE3D_NS::RenderHandleComponent>())) {
48 bool changed = false;
49 if (!p) {
50 p = interface_pointer_cast<IShader>(scene->CreateObject(ClassId::Shader));
51 changed = true;
52 }
53 auto sha = rhman->GetRenderHandleReference(v.shader);
54 auto gs = rhman->GetRenderHandleReference(v.graphicsState);
55
56 if (auto i = interface_cast<IRenderResource>(p)) {
57 changed |= i->GetRenderHandle().GetHandle() != sha.GetHandle();
58 }
59 if (auto i = interface_cast<IGraphicsState>(p)) {
60 changed |= i->GetGraphicsState().GetHandle() != gs.GetHandle();
61 }
62 if (auto i = interface_cast<IShaderState>(p); changed && i) {
63 i->SetShaderState(sha, gs);
64 }
65 }
66 })
67 .Wait();
68 }
69 return p;
70 }
ConvertToTarget__anon1fbf93410111::ShaderConverter71 TargetType ConvertToTarget(const SourceType& v) const
72 {
73 TargetType res;
74 if (auto scene = scene_.lock()) {
75 if (!v) {
76 res.shader = {};
77 res.graphicsState = {};
78 } else {
79 if (auto i = interface_cast<IRenderResource>(v)) {
80 res.shader = HandleFromRenderResource(scene, i->GetRenderHandle());
81 }
82 if (auto i = interface_cast<IGraphicsState>(v)) {
83 res.graphicsState = HandleFromRenderResource(scene, i->GetGraphicsState());
84 }
85 }
86 }
87 return res;
88 }
89
90 private:
91 IInternalScene::WeakPtr scene_;
92 };
93 struct RenderSortConverter {
94 using SourceType = SCENE_NS::RenderSort;
95 using TargetType = CORE3D_NS::MaterialComponent::RenderSort;
96
ConvertToSource__anon1fbf93410111::RenderSortConverter97 SourceType ConvertToSource(META_NS::IAny&, const TargetType& v) const
98 {
99 return SourceType { v.renderSortLayer, v.renderSortLayerOrder };
100 }
ConvertToTarget__anon1fbf93410111::RenderSortConverter101 TargetType ConvertToTarget(const SourceType& v) const
102 {
103 return TargetType { v.renderSortLayer, v.renderSortLayerOrder };
104 }
105 };
106 struct LightingFlagsConverter {
107 using SourceType = SCENE_NS::LightingFlags;
108 using TargetType = uint32_t;
109
ConvertToSource__anon1fbf93410111::LightingFlagsConverter110 SourceType ConvertToSource(META_NS::IAny&, const TargetType& v) const
111 {
112 return static_cast<SourceType>(v);
113 }
ConvertToTarget__anon1fbf93410111::LightingFlagsConverter114 TargetType ConvertToTarget(const SourceType& v) const
115 {
116 return static_cast<TargetType>(v);
117 }
118 };
119 } // namespace
120
CreateEntity(const IInternalScene::Ptr & scene)121 CORE_NS::Entity Material::CreateEntity(const IInternalScene::Ptr& scene)
122 {
123 const auto& ecs = scene->GetEcsContext().GetNativeEcs();
124 const auto materialManager = CORE_NS::GetManager<CORE3D_NS::IMaterialComponentManager>(*ecs);
125 if (!materialManager) {
126 return {};
127 }
128 const auto nameManager = CORE_NS::GetManager<CORE3D_NS::INameComponentManager>(*ecs);
129 if (!nameManager) {
130 return {};
131 }
132 auto ent = ecs->GetEntityManager().Create();
133 materialManager->Create(ent);
134 nameManager->Create(ent);
135 return ent;
136 }
137
SetEcsObject(const IEcsObject::Ptr & obj)138 bool Material::SetEcsObject(const IEcsObject::Ptr& obj)
139 {
140 shaderChanged_.Unsubscribe();
141 if (Super::SetEcsObject(obj)) {
142 if (auto att = GetSelf<META_NS::IAttach>()) {
143 if (auto attc = att->GetAttachmentContainer(false)) {
144 if (auto c = attc->FindByName<IInternalMaterial>("MaterialComponent")) {
145 material_ = c;
146 }
147 }
148 }
149 if (!material_) {
150 auto p = META_NS::GetObjectRegistry().Create<IInternalMaterial>(ClassId::MaterialComponent);
151 if (auto acc = interface_cast<IEcsObjectAccess>(p)) {
152 if (acc->SetEcsObject(obj)) {
153 material_ = p;
154 IAttach::Attach(material_);
155 }
156 }
157 }
158 }
159 return material_ != nullptr;
160 }
161
InitDynamicProperty(const META_NS::IProperty::Ptr & p,BASE_NS::string_view path)162 bool Material::InitDynamicProperty(const META_NS::IProperty::Ptr& p, BASE_NS::string_view path)
163 {
164 BASE_NS::string name = p->GetName();
165 if (name == "MaterialShader") {
166 auto ep = material_->MaterialShader();
167 shaderChanged_.Subscribe(ep, [this]() {
168 UpdateTextures(nullptr);
169 UpdateCustoms(nullptr);
170 });
171
172 auto i = interface_cast<META_NS::IStackProperty>(p);
173 return ep && i &&
174 i->PushValue(META_NS::IValue::Ptr(new ConvertingValue<ShaderConverter>(ep, { object_->GetScene() })));
175 }
176 if (name == "DepthShader") {
177 auto ep = material_->DepthShader();
178 auto i = interface_cast<META_NS::IStackProperty>(p);
179 return ep && i &&
180 i->PushValue(META_NS::IValue::Ptr(new ConvertingValue<ShaderConverter>(ep, { object_->GetScene() })));
181 }
182 if (name == "Textures") {
183 UpdateTextures(p).Wait();
184 return true;
185 }
186 if (name == "CustomProperties") {
187 UpdateCustoms(p).Wait();
188 return true;
189 }
190 if (name == "RenderSort") {
191 auto ep = material_->RenderSort();
192 auto i = interface_cast<META_NS::IStackProperty>(p);
193 return ep && i && i->PushValue(META_NS::IValue::Ptr(new ConvertingValue<RenderSortConverter>(ep)));
194 }
195 if (name == "LightingFlags") {
196 auto ep = material_->LightingFlags();
197 auto i = interface_cast<META_NS::IStackProperty>(p);
198 return ep && i && i->PushValue(META_NS::IValue::Ptr(new ConvertingValue<LightingFlagsConverter>(ep)));
199 }
200 return false;
201 }
202
UpdateTextures(const META_NS::IProperty::Ptr & p)203 Future<bool> Material::UpdateTextures(const META_NS::IProperty::Ptr& p)
204 {
205 auto property = p;
206 if (!property) {
207 property = GetProperty("Textures", META_NS::MetadataQuery::EXISTING);
208 }
209 if (const auto obj = GetEcsObject(); obj && property) {
210 if (!textureSyncScheduled_.exchange(true)) {
211 return obj->GetScene()->AddTask([this, property]() {
212 if (textureSyncScheduled_.load()) {
213 bool success =
214 ConstructTextures(property); // Avoid callback due to ConstructTextures changing target
215 textureSyncScheduled_ = false;
216 }
217 return false;
218 });
219 }
220 }
221 return {};
222 }
223
UpdateCustoms(const META_NS::IProperty::Ptr & p)224 Future<bool> Material::UpdateCustoms(const META_NS::IProperty::Ptr& p)
225 {
226 auto property = p;
227 if (!property) {
228 property = GetProperty("CustomProperties", META_NS::MetadataQuery::EXISTING);
229 }
230 if (const auto obj = GetEcsObject(); obj && property) {
231 if (!customsSyncScheduled_.exchange(true)) {
232 return obj->GetScene()->AddTask([this, property]() {
233 if (customsSyncScheduled_.load()) {
234 bool success =
235 UpdateCustomProperties(property); // Avoid callback due to ConstructTextures changing target
236 customsSyncScheduled_ = false;
237 }
238 return false;
239 });
240 }
241 }
242 return {};
243 }
244
245 // clang-format off
246 const char* const METALLIC_ROUGHNESS_TEXTURE_NAMES[] = {
247 "BASE_COLOR",
248 "NORMAL",
249 "MATERIAL",
250 "EMISSIVE",
251 "AO",
252 "CLEARCOAT",
253 "CLEARCOAT_ROUGHNESS",
254 "CLEARCOAT_NORMAL",
255 "SHEEN",
256 "TRANSMISSION",
257 "SPECULAR"
258 };
259 const char* const METALLIC_ROUGHNESS_ECS_TEXTURE_NAMES[] = {
260 "baseColor",
261 "normal",
262 "material",
263 "emissive",
264 "ambientOcclusion",
265 "clearcoat",
266 "clearcoatRoughness",
267 "clearcoatNormal",
268 "sheen",
269 "transmission",
270 "specular"
271 };
272 constexpr size_t TEXTURE_NAMES_SIZE = sizeof(METALLIC_ROUGHNESS_TEXTURE_NAMES) / sizeof(*METALLIC_ROUGHNESS_TEXTURE_NAMES);
273 // clang-format on
274
UpdateTextureNames(const BASE_NS::vector<ITexture::Ptr> & textures,const IInternalMaterial::ActiveTextureSlotInfo & tsi)275 bool Material::UpdateTextureNames(
276 const BASE_NS::vector<ITexture::Ptr>& textures, const IInternalMaterial::ActiveTextureSlotInfo& tsi)
277 {
278 bool updated = false;
279 for (size_t i = 0; i < textures.size(); ++i) {
280 if (auto& t = textures[i]) {
281 if (auto named = interface_cast<META_NS::INamed>(t)) {
282 auto nameProp = named->Name();
283 const auto& name = tsi.slots[i].name;
284 if (i < TEXTURE_NAMES_SIZE && name == METALLIC_ROUGHNESS_ECS_TEXTURE_NAMES[i]) {
285 updated |=
286 nameProp->SetDefaultValue(METALLIC_ROUGHNESS_TEXTURE_NAMES[i]) == META_NS::AnyReturn::SUCCESS;
287 } else {
288 // Different name to the default ecs texture slot name
289 updated |= nameProp->SetDefaultValue(name) == META_NS::AnyReturn::SUCCESS;
290 }
291 }
292 }
293 }
294 return updated;
295 }
296
ConstructTextures(const META_NS::IProperty::Ptr & p)297 bool Material::ConstructTextures(const META_NS::IProperty::Ptr& p)
298 {
299 auto textures = META_NS::ArrayProperty<ITexture::Ptr>(p);
300 if (!textures || !material_) {
301 return false;
302 }
303 BASE_NS::vector<ITexture::Ptr> texs = textures->GetValue();
304 auto tsi = material_->GetActiveTextureSlotInfo();
305 if (texs.size() == tsi.count) {
306 if (UpdateTextureNames(texs, tsi)) {
307 textures->NotifyChange();
308 }
309 return true;
310 }
311 auto mtex = material_->Textures()->GetValue();
312 texs.resize(tsi.count);
313 auto& r = META_NS::GetObjectRegistry();
314 for (size_t i = 0; i < texs.size(); ++i) {
315 auto& t = texs[i];
316 if (!t) {
317 t = r.Create<ITexture>(ClassId::Texture);
318 if (auto si = interface_cast<IArrayElementIndex>(t)) {
319 si->SetIndex(i);
320 }
321 if (auto access = interface_cast<IEcsObjectAccess>(t)) {
322 access->SetEcsObject(GetEcsObject());
323 }
324 texs[i] = t;
325 }
326 if (!t) {
327 CORE_LOG_E("Failed to create texture for material");
328 return false;
329 }
330 }
331 UpdateTextureNames(texs, tsi);
332 if (!texs.empty()) {
333 textures->SetValue(texs);
334 } else {
335 textures->ResetValue();
336 }
337 return true;
338 }
339
UpdateCustomProperties(const META_NS::IProperty::Ptr & p)340 bool Material::UpdateCustomProperties(const META_NS::IProperty::Ptr& p)
341 {
342 auto ecsobj = GetEcsObject();
343 auto ccs = META_NS::Property<META_NS::IMetadata::Ptr>(p);
344 if (!ccs || !material_ || !ecsobj) {
345 return false;
346 }
347 auto meta = ccs->GetValue();
348 if (!meta) {
349 meta = META_NS::GetObjectRegistry().Create<META_NS::IMetadata>(META_NS::ClassId::Object);
350 }
351 std::set<BASE_NS::string> props;
352 BASE_NS::vector<META_NS::IEngineValue::Ptr> values;
353 if (SyncCustomProperties(&values)) {
354 for (auto&& v : values) {
355 BASE_NS::string pname(SCENE_NS::PropertyName(v->GetName()));
356 auto p = meta->GetProperty(pname);
357 if (p && GetEngineValueFromProperty(p) != v) {
358 auto iany = META_NS::GetInternalAny(p);
359 auto eval = interface_cast<META_NS::IValue>(v);
360 if (!iany || !eval || !META_NS::IsCompatibleWith(*iany, eval->GetValue())) {
361 CORE_LOG_W("Removing incompatible custom property: %s", pname.c_str());
362 meta->RemoveProperty(p);
363 p = nullptr;
364 }
365 }
366 if (p) {
367 if (GetEngineValueFromProperty(p) != v) {
368 auto value = p->GetValue().Clone();
369 if (value && ecsobj->AttachProperty(p, v).GetResult()) {
370 // set the value from the users property
371 p->SetValue(*value);
372 } else {
373 CORE_LOG_W("Failed to attach custom property: %s", pname.c_str());
374 meta->RemoveProperty(p);
375 }
376 }
377 } else {
378 p = ecsobj->CreateProperty(v).GetResult();
379 if (p) {
380 meta->AddProperty(p);
381 }
382 }
383 props.insert(pname);
384 }
385 for (auto&& p : meta->GetProperties()) {
386 // we remove properties with engine access, user added, not yet attached properties are left
387 if (!props.count(p->GetName()) && GetEngineValueFromProperty(p)) {
388 meta->RemoveProperty(p);
389 }
390 }
391 ccs->SetValue(meta);
392 }
393 return true;
394 }
395
GetCustomProperties() const396 META_NS::IMetadata::Ptr Material::GetCustomProperties() const
397 {
398 return CustomProperties()->GetValue();
399 }
400
GetCustomProperty(BASE_NS::string_view name) const401 META_NS::IProperty::Ptr Material::GetCustomProperty(BASE_NS::string_view name) const
402 {
403 const auto obj = GetEcsObject();
404 if (!obj) {
405 return nullptr;
406 }
407 const auto manager = obj->GetEngineValueManager();
408 if (!manager) {
409 return nullptr;
410 }
411
412 if (!SyncCustomProperties({})) {
413 CORE_LOG_W("Syncing properties to engine values failed. Using old values if available.");
414 }
415 BASE_NS::string fullName { name };
416 if (!name.starts_with("MaterialComponent.customProperties.")) {
417 fullName = "MaterialComponent.customProperties." + fullName;
418 }
419 return META_NS::PropertyFromEngineValue(fullName, manager->GetEngineValue(fullName));
420 }
421
SyncCustomProperties(BASE_NS::vector<META_NS::IEngineValue::Ptr> * synced_values) const422 bool Material::SyncCustomProperties(BASE_NS::vector<META_NS::IEngineValue::Ptr>* synced_values) const
423 {
424 const auto obj = GetEcsObject();
425 if (!material_ || !obj) {
426 return false;
427 }
428 const auto manager = obj->GetEngineValueManager();
429 const auto allCustomProps = META_NS::GetEngineValueFromProperty(material_->CustomProperties());
430 if (!manager || !allCustomProps) {
431 return false;
432 }
433
434 auto doSync = [&]() -> bool {
435 // syncing material shader updates the custom properties
436 obj->GetScene()->SyncProperty(material_->MaterialShader(), META_NS::EngineSyncDirection::AUTO);
437 obj->GetScene()->SyncProperty(material_->CustomProperties(), META_NS::EngineSyncDirection::FROM_ENGINE);
438 return manager->ConstructValues(allCustomProps, { "", synced_values });
439 };
440 return obj->GetScene()->AddTask(doSync).Wait();
441 }
442
CopyTextureData(const META_NS::IProperty::ConstPtr & p)443 void Material::CopyTextureData(const META_NS::IProperty::ConstPtr& p)
444 {
445 if (META_NS::ArrayProperty<const ITexture::Ptr> arr { p }) {
446 for (size_t i = 0; i != arr->GetSize(); ++i) {
447 if (auto in = interface_cast<META_NS::IMetadata>(arr->GetValueAt(i))) {
448 if (auto out = interface_cast<META_NS::IMetadata>(Textures()->GetValueAt(i))) {
449 META_NS::CopyToDefaultAndReset(*in, *out);
450 }
451 }
452 }
453 }
454 }
455
SetResource(const CORE_NS::IResource::Ptr & p)456 bool Material::SetResource(const CORE_NS::IResource::Ptr& p)
457 {
458 auto ores = interface_cast<META_NS::IObjectResource>(p);
459 if (!ores || p->GetResourceType() != resourceType_.ToUid()) {
460 CORE_LOG_W("Invalid resource type");
461 return false;
462 }
463 if (auto resm = interface_cast<META_NS::IMetadata>(p)) {
464 if (auto m = static_cast<META_NS::IMetadata*>(this)) {
465 for (auto&& p : resm->GetProperties()) {
466 if (p->GetName() == "Textures") {
467 CopyTextureData(p);
468 } else if (p->GetName() == "CustomProperties") {
469 // skip for now
470 } else {
471 META_NS::CopyToDefaultAndReset(p, *m);
472 }
473 }
474 }
475 }
476 std::unique_lock lock { mutex_ };
477 resource_ = p->GetResourceId();
478 return true;
479 }
CreateResource() const480 CORE_NS::IResource::Ptr Material::CreateResource() const
481 {
482 auto r = Super::CreateResource();
483 if (auto i = interface_cast<META_NS::IMetadata>(r)) {
484 if (auto p = i->GetProperty("CustomProperties")) {
485 i->RemoveProperty(p);
486 }
487 }
488 return r;
489 }
490
491 SCENE_END_NAMESPACE()
492