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 "MaterialJS.h"
16
17 #include <meta/api/make_callback.h>
18 #include <meta/interface/builtin_objects.h>
19 #include <meta/interface/intf_task_queue.h>
20 #include <meta/interface/intf_task_queue_registry.h>
21 #include <meta/interface/property/construct_property.h>
22 #include <scene/interface/intf_shader.h>
23 #include <scene/interface/intf_scene.h>
24 #include <scene/interface/intf_material.h>
25
26 #include "SceneJS.h"
27 using IntfPtr = META_NS::SharedPtrIInterface;
28 using IntfWeakPtr = META_NS::WeakPtrIInterface;
29
BaseMaterial(MaterialType lt)30 BaseMaterial::BaseMaterial(MaterialType lt) : SceneResourceImpl(SceneResourceImpl::MATERIAL), materialType_(lt) {}
~BaseMaterial()31 BaseMaterial::~BaseMaterial() {}
Init(const char * class_name,napi_env env,napi_value exports,napi_callback ctor,BASE_NS::vector<napi_property_descriptor> & node_props)32 void BaseMaterial::Init(const char* class_name, napi_env env, napi_value exports, napi_callback ctor,
33 BASE_NS::vector<napi_property_descriptor>& node_props)
34 {
35 SceneResourceImpl::GetPropertyDescs(node_props);
36
37 using namespace NapiApi;
38 node_props.emplace_back(TROGetProperty<uint32_t, BaseMaterial, &BaseMaterial::GetMaterialType>("materialType"));
39 node_props.emplace_back(
40 TROGetSetProperty<bool, BaseMaterial, &BaseMaterial::GetShadowReceiver, &BaseMaterial::SetShadowReceiver>(
41 "shadowReceiver"));
42 node_props.emplace_back(
43 TROGetSetProperty<uint32_t, BaseMaterial, &BaseMaterial::GetCullMode, &BaseMaterial::SetCullMode>("cullMode"));
44 node_props.emplace_back(
45 TROGetSetProperty<NapiApi::Object, BaseMaterial, &BaseMaterial::GetBlend, &BaseMaterial::SetBlend>("blend"));
46 node_props.emplace_back(
47 TROGetSetProperty<float, BaseMaterial, &BaseMaterial::GetAlphaCutoff, &BaseMaterial::SetAlphaCutoff>(
48 "alphaCutoff"));
49 node_props.emplace_back(
50 TROGetSetProperty<NapiApi::Object, BaseMaterial, &BaseMaterial::GetRenderSort, &BaseMaterial::SetRenderSort>(
51 "renderSort"));
52
53 napi_value func;
54 auto status = napi_define_class(
55 env, class_name, NAPI_AUTO_LENGTH, ctor, nullptr, node_props.size(), node_props.data(), &func);
56
57 NapiApi::MyInstanceState* mis;
58 NapiApi::MyInstanceState::GetInstance(env, (void**)&mis);
59 if (mis) {
60 mis->StoreCtor(class_name, func);
61 }
62
63 NapiApi::Object exp1(env, exports);
64 napi_value eType1 = nullptr;
65 napi_value v1 = nullptr;
66 napi_create_object(env, &eType1);
67 #define DECL_ENUM(enu, x) \
68 napi_create_uint32(env, MaterialType::x, &v1); \
69 napi_set_named_property(env, enu, #x, v1);
70
71 DECL_ENUM(eType1, SHADER);
72 DECL_ENUM(eType1, METALLIC_ROUGHNESS);
73 #undef DECL_ENUM
74 exp1.Set("MaterialType", eType1);
75
76 NapiApi::Object exp2(env, exports);
77 napi_value eType2 = nullptr;
78 napi_value v2 = nullptr;
79 napi_create_object(env, &eType2);
80 #define DECL_ENUM(enu, x) \
81 napi_create_uint32(env, CullMode::x, &v2); \
82 napi_set_named_property(env, enu, #x, v2);
83
84 DECL_ENUM(eType2, NONE);
85 DECL_ENUM(eType2, FRONT);
86 DECL_ENUM(eType2, BACK);
87 #undef DECL_ENUM
88 exp2.Set("CullMode", eType2);
89 }
90
GetInstanceImpl(uint32_t id)91 void* BaseMaterial::GetInstanceImpl(uint32_t id)
92 {
93 if (id == BaseMaterial::ID)
94 return (BaseMaterial*)this;
95 return SceneResourceImpl::GetInstanceImpl(id);
96 }
DisposeNative(BaseObject * tro)97 void BaseMaterial::DisposeNative(BaseObject* tro)
98 {
99 // do nothing for now..
100 LOG_V("BaseMaterial::DisposeNative");
101 tro->UnsetNativeObject();
102 scene_.Reset();
103 }
GetMaterialType(NapiApi::FunctionContext<> & ctx)104 napi_value BaseMaterial::GetMaterialType(NapiApi::FunctionContext<>& ctx)
105 {
106 if (!validateSceneRef()) {
107 return ctx.GetUndefined();
108 }
109 uint32_t type = -1; // return -1 if the object does not exist anymore
110 if (auto material = ctx.This().GetNative<SCENE_NS::IMaterial>()) {
111 type = META_NS::GetValue(material->Type()) == SCENE_NS::MaterialType::METALLIC_ROUGHNESS
112 ? BaseMaterial::METALLIC_ROUGHNESS
113 : BaseMaterial::SHADER;
114 }
115 return ctx.GetNumber(type);
116 }
GetShadowReceiver(NapiApi::FunctionContext<> & ctx)117 napi_value BaseMaterial::GetShadowReceiver(NapiApi::FunctionContext<>& ctx)
118 {
119 if (!validateSceneRef()) {
120 return ctx.GetUndefined();
121 }
122 bool shadowReceiver = false;
123 if (auto material = ctx.This().GetNative<SCENE_NS::IMaterial>()) {
124 uint32_t lightingFlags = uint32_t(META_NS::GetValue(material->LightingFlags()));
125 shadowReceiver = lightingFlags & uint32_t(SCENE_NS::LightingFlags::SHADOW_RECEIVER_BIT);
126 }
127 return ctx.GetBoolean(shadowReceiver);
128 }
SetShadowReceiver(NapiApi::FunctionContext<bool> & ctx)129 void BaseMaterial::SetShadowReceiver(NapiApi::FunctionContext<bool>& ctx)
130 {
131 if (!validateSceneRef()) {
132 return;
133 }
134 bool shadowReceiver = ctx.Arg<0>();
135 if (auto material = ctx.This().GetNative<SCENE_NS::IMaterial>()) {
136 uint32_t lightingFlags = uint32_t(META_NS::GetValue(material->LightingFlags()));
137 if (shadowReceiver) {
138 lightingFlags |= uint32_t(SCENE_NS::LightingFlags::SHADOW_RECEIVER_BIT);
139 } else {
140 lightingFlags &= ~uint32_t(SCENE_NS::LightingFlags::SHADOW_RECEIVER_BIT);
141 }
142 META_NS::SetValue(material->LightingFlags(), static_cast<SCENE_NS::LightingFlags>(lightingFlags));
143 }
144 }
GetCullMode(NapiApi::FunctionContext<> & ctx)145 napi_value BaseMaterial::GetCullMode(NapiApi::FunctionContext<>& ctx)
146 {
147 if (!validateSceneRef()) {
148 return ctx.GetUndefined();
149 }
150 auto cullMode = SCENE_NS::CullModeFlags::NONE;
151 if (auto material = ctx.This().GetNative<SCENE_NS::IMaterial>()) {
152 if (auto shader = META_NS::GetValue(material->MaterialShader())) {
153 if (auto scene = scene_.GetObject().GetNative<SCENE_NS::IScene>()) {
154 cullMode = static_cast<SCENE_NS::CullModeFlags>(shader->CullMode()->GetValue());
155 }
156 }
157 }
158 return ctx.GetNumber(static_cast<uint32_t>(cullMode));
159 }
SetCullMode(NapiApi::FunctionContext<uint32_t> & ctx)160 void BaseMaterial::SetCullMode(NapiApi::FunctionContext<uint32_t>& ctx)
161 {
162 if (!validateSceneRef()) {
163 return;
164 }
165 auto cullMode = static_cast<SCENE_NS::CullModeFlags>((uint32_t)ctx.Arg<0>());
166
167 if (auto material = ctx.This().GetNative<SCENE_NS::IMaterial>()) {
168 if (auto shader = META_NS::GetValue(material->MaterialShader())) {
169 shader->CullMode()->SetValue(cullMode);
170 // need to forcefully refresh the material, otherwise renderer will ignore the change
171 auto val = META_NS::GetValue(material->MaterialShader());
172 META_NS::SetValue(material->MaterialShader(), val);
173 }
174 }
175 }
GetBlend(NapiApi::FunctionContext<> & ctx)176 napi_value BaseMaterial::GetBlend(NapiApi::FunctionContext<>& ctx)
177 {
178 if (!validateSceneRef()) {
179 return ctx.GetUndefined();
180 }
181
182 auto material = ctx.This().GetNative<SCENE_NS::IMaterial>();
183 if (!material) {
184 return ctx.GetUndefined();
185 }
186
187 auto shader = META_NS::GetValue(material->MaterialShader());
188 if (!shader) {
189 return ctx.GetUndefined();
190 }
191
192 bool enableBlend = shader->Blend()->GetValue();
193
194 NapiApi::Object blendObjectJS(ctx.Env());
195 blendObjectJS.Set("enabled", ctx.GetBoolean(enableBlend));
196 return blendObjectJS.ToNapiValue();
197 }
SetBlend(NapiApi::FunctionContext<NapiApi::Object> & ctx)198 void BaseMaterial::SetBlend(NapiApi::FunctionContext<NapiApi::Object>& ctx)
199 {
200 if (!validateSceneRef()) {
201 return;
202 }
203
204 auto material = ctx.This().GetNative<SCENE_NS::IMaterial>();
205 if (!material) {
206 // material destroyed, just make sure we have no shader reference anymore.
207 return;
208 }
209
210 auto shader = META_NS::GetValue(material->MaterialShader());
211 if (!shader) {
212 return;
213 }
214
215 NapiApi::Object blendObjectJS = ctx.Arg<0>();
216 bool enableBlend = blendObjectJS.Get<bool>("enabled");
217 shader->Blend()->SetValue(enableBlend);
218
219 // need to forcefully refresh the material, otherwise renderer will ignore the change
220 auto val = META_NS::GetValue(material->MaterialShader());
221 META_NS::SetValue(material->MaterialShader(), val);
222 }
GetAlphaCutoff(NapiApi::FunctionContext<> & ctx)223 napi_value BaseMaterial::GetAlphaCutoff(NapiApi::FunctionContext<>& ctx)
224 {
225 if (!validateSceneRef()) {
226 return ctx.GetUndefined();
227 }
228 float alphaCutoff = 1.0f;
229 if (auto material = ctx.This().GetNative<SCENE_NS::IMaterial>()) {
230 alphaCutoff = META_NS::GetValue(material->AlphaCutoff());
231 }
232 return ctx.GetNumber(alphaCutoff);
233 }
SetAlphaCutoff(NapiApi::FunctionContext<float> & ctx)234 void BaseMaterial::SetAlphaCutoff(NapiApi::FunctionContext<float>& ctx)
235 {
236 if (!validateSceneRef()) {
237 return;
238 }
239 float alphaCutoff = ctx.Arg<0>();
240
241 if (auto material = ctx.This().GetNative<SCENE_NS::IMaterial>()) {
242 META_NS::SetValue(material->AlphaCutoff(), alphaCutoff);
243 }
244 }
GetRenderSort(NapiApi::FunctionContext<> & ctx)245 napi_value BaseMaterial::GetRenderSort(NapiApi::FunctionContext<>& ctx)
246 {
247 if (!validateSceneRef()) {
248 return ctx.GetUndefined();
249 }
250
251 auto material = ctx.This().GetNative<SCENE_NS::IMaterial>();
252 if (!material) {
253 return ctx.GetUndefined();
254 }
255
256 auto renderSort = META_NS::GetValue(material->RenderSort());
257
258 NapiApi::Object renderSortJS(ctx.Env());
259 renderSortJS.Set("renderSortLayer", ctx.GetNumber(renderSort.renderSortLayer));
260 renderSortJS.Set("renderSortLayerOrder", ctx.GetNumber(renderSort.renderSortLayerOrder));
261 return renderSortJS.ToNapiValue();
262 }
SetRenderSort(NapiApi::FunctionContext<NapiApi::Object> & ctx)263 void BaseMaterial::SetRenderSort(NapiApi::FunctionContext<NapiApi::Object>& ctx)
264 {
265 if (!validateSceneRef()) {
266 return;
267 }
268
269 auto material = ctx.This().GetNative<SCENE_NS::IMaterial>();
270 if (!material) {
271 // material destroyed, just make sure we have no shader reference anymore.
272 return;
273 }
274
275 SCENE_NS::RenderSort renderSort;
276 NapiApi::Object renderSortJS = ctx.Arg<0>();
277 renderSort.renderSortLayer = renderSortJS.Get<uint32_t>("renderSortLayer");
278 renderSort.renderSortLayerOrder = renderSortJS.Get<uint32_t>("renderSortLayerOrder");
279 META_NS::SetValue(material->RenderSort(), renderSort);
280 }
281
282 //------
283
Init(napi_env env,napi_value exports)284 void MaterialJS::Init(napi_env env, napi_value exports)
285 {
286 #define MRADDPROP(index, name) \
287 NapiApi::GetSetProperty<NapiApi::Object, MaterialJS, \
288 &MaterialJS::GetMaterialProperty<index>, \
289 &MaterialJS::SetMaterialProperty<index>>(name)
290
291 BASE_NS::vector<napi_property_descriptor> props = {
292 NapiApi::GetSetProperty<NapiApi::Object, MaterialJS, &MaterialJS::GetColorShader,
293 &MaterialJS::SetColorShader>("colorShader"),
294 MRADDPROP(0, "baseColor"),
295 MRADDPROP(1, "normal"),
296 MRADDPROP(2, "material"),
297 MRADDPROP(3, "emissive"),
298 MRADDPROP(4, "ambientOcclusion"),
299 MRADDPROP(5, "clearCoat"),
300 MRADDPROP(6, "clearCoatRoughness"),
301 MRADDPROP(7, "clearCoatNormal"),
302 MRADDPROP(8, "sheen"),
303 MRADDPROP(10, "specular")
304 };
305 #undef MRADDPROP
306
307 BaseMaterial::Init("Material", env, exports, BaseObject::ctor<MaterialJS>(), props);
308 }
309
MaterialJS(napi_env e,napi_callback_info i)310 MaterialJS::MaterialJS(napi_env e, napi_callback_info i)
311 : BaseObject(e, i), BaseMaterial(BaseMaterial::MaterialType::METALLIC_ROUGHNESS)
312 {
313 NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object> fromJs(e, i);
314 NapiApi::Object meJs(fromJs.This());
315
316 NapiApi::Object scene = fromJs.Arg<0>(); // access to owning scene... (do i need it here?)
317 NapiApi::Object args = fromJs.Arg<1>(); // other args
318
319 scene_ = scene;
320 if (!scene_.GetObject().GetNative<SCENE_NS::IScene>()) {
321 LOG_F("INVALID SCENE!");
322 }
323
324 if (const auto sceneJS = scene.GetJsWrapper<SceneJS>()) {
325 sceneJS->DisposeHook(reinterpret_cast<uintptr_t>(&scene_), meJs);
326 }
327
328 const auto material = GetNativeObject<SCENE_NS::IMaterial>();
329 if (material) {
330 materialType_ = META_NS::GetValue(material->Type()) == SCENE_NS::MaterialType::CUSTOM
331 ? MaterialType::SHADER
332 : MaterialType::METALLIC_ROUGHNESS;
333 }
334
335 BASE_NS::string name;
336 if (auto prm = args.Get<BASE_NS::string>("name"); prm.IsDefined()) {
337 name = prm;
338 } else {
339 if (auto named = interface_cast<META_NS::IObject>(material)) {
340 name = named->GetName();
341 }
342 }
343 meJs.Set("name", name);
344 }
345
~MaterialJS()346 MaterialJS::~MaterialJS() {}
GetInstanceImpl(uint32_t id)347 void* MaterialJS::GetInstanceImpl(uint32_t id)
348 {
349 if (id == MaterialJS::ID)
350 return this;
351 return BaseMaterial::GetInstanceImpl(id);
352 }
DisposeNative(void * scn)353 void MaterialJS::DisposeNative(void* scn)
354 {
355 if (disposed_) {
356 return;
357 }
358 disposed_ = true;
359 if (auto* sceneJS = static_cast<SceneJS*>(scn)) {
360 sceneJS->ReleaseDispose(reinterpret_cast<uintptr_t>(&scene_));
361 }
362
363 UnsetNativeObject();
364 shaderBind_.reset();
365 shader_.Reset();
366
367 BaseMaterial::DisposeNative(this);
368 }
Finalize(napi_env env)369 void MaterialJS::Finalize(napi_env env)
370 {
371 DisposeNative(scene_.GetObject().GetJsWrapper<SceneJS>());
372 BaseObject::Finalize(env);
373 }
SetColorShader(NapiApi::FunctionContext<NapiApi::Object> & ctx)374 void MaterialJS::SetColorShader(NapiApi::FunctionContext<NapiApi::Object>& ctx)
375 {
376 if (!validateSceneRef()) {
377 // owning scene has been destroyed.
378 // the material etc should also be gone.
379 // but there is a possible issue where the native object is still alive.
380
381 // most likely could happen if scene.dispose is not called
382 // but all references to the scene have been released,
383 // and garbage collection may or may not have been done yet. (or is partially done)
384 // if the scene is garbage collected then all the resources should be disposed.
385 return;
386 }
387
388 NapiApi::Object shaderJS = ctx.Arg<0>();
389 auto material = interface_pointer_cast<SCENE_NS::IMaterial>(GetNativeObject());
390 if (!material) {
391 // material destroyed, just make sure we have no shader reference anymore.
392 shader_.Reset();
393 return;
394 }
395 auto shader = shaderJS.GetNative<SCENE_NS::IShader>();
396 materialType_ = shader ? MaterialType::SHADER : MaterialType::METALLIC_ROUGHNESS;
397 META_NS::SetValue(material->Type(),
398 shader ? SCENE_NS::MaterialType::CUSTOM : SCENE_NS::MaterialType::METALLIC_ROUGHNESS);
399
400 // bind it to material (in native)
401 material->MaterialShader()->SetValue(shader);
402
403 if (!shader) {
404 shader_.Reset();
405 return;
406 }
407
408 // construct a "bound" shader object from the "non bound" one.
409 NapiApi::Env env(ctx.Env());
410 NapiApi::Object parms(env);
411 napi_value args[] = {
412 scene_.GetValue(), // bind the new instance of the shader to this javascript scene object
413 parms.ToNapiValue() // other constructor parameters
414 };
415
416 parms.Set("name", shaderJS.Get("name"));
417 parms.Set("Material", ctx.This()); // js material object that we are bound to.
418
419 shaderBind_ = META_NS::GetObjectRegistry().Create(META_NS::ClassId::Object);
420 interface_cast<META_NS::IMetadata>(shaderBind_)
421 ->AddProperty(META_NS::ConstructProperty<IntfPtr>(
422 "shader", nullptr, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
423 interface_cast<META_NS::IMetadata>(shaderBind_)
424 ->GetProperty<IntfPtr>("shader")
425 ->SetValue(interface_pointer_cast<CORE_NS::IInterface>(shader));
426
427 auto result = CreateFromNativeInstance(env, "Shader", shaderBind_, PtrType::WEAK, args);
428 shader_ = NapiApi::StrongRef(result);
429 }
GetColorShader(NapiApi::FunctionContext<> & ctx)430 napi_value MaterialJS::GetColorShader(NapiApi::FunctionContext<>& ctx)
431 {
432 if (!validateSceneRef()) {
433 return ctx.GetUndefined();
434 }
435
436 auto material = interface_pointer_cast<SCENE_NS::IMaterial>(GetNativeObject());
437 if (!material) {
438 // material destroyed, just make sure we have no shader reference anymore.
439 shader_.Reset();
440 return ctx.GetNull();
441 }
442
443 if (shader_.IsEmpty()) {
444 // no shader set yet..
445 // see if we have one on the native side.
446 // and create the "bound shader" object from it.
447
448 // check native side..
449 SCENE_NS::IShader::Ptr shader = material->MaterialShader()->GetValue();
450 if (!shader) {
451 // no shader in native also.
452 return ctx.GetNull();
453 }
454
455 // construct a "bound" shader object from the "non bound" one.
456 NapiApi::Env env(ctx.Env());
457 NapiApi::Object parms(env);
458 napi_value args[] = {
459 scene_.GetValue(), // bind the new instance of the shader to this javascript scene object
460 parms.ToNapiValue() // other constructor parameters
461 };
462
463 if (!scene_.GetObject().GetNative<SCENE_NS::IScene>()) {
464 LOG_F("INVALID SCENE!");
465 }
466 parms.Set("Material", ctx.This()); // js material object that we are bound to.
467
468 shaderBind_ = META_NS::GetObjectRegistry().Create(META_NS::ClassId::Object);
469 interface_cast<META_NS::IMetadata>(shaderBind_)
470 ->AddProperty(META_NS::ConstructProperty<IntfPtr>(
471 "shader", nullptr, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
472 interface_cast<META_NS::IMetadata>(shaderBind_)
473 ->GetProperty<IntfPtr>("shader")
474 ->SetValue(interface_pointer_cast<CORE_NS::IInterface>(shader));
475
476 auto result = CreateFromNativeInstance(env, "Shader", shaderBind_, PtrType::WEAK, args);
477 shader_ = NapiApi::StrongRef(result);
478 }
479 return shader_.GetValue();
480 }
481
482 template<size_t Index>
GetMaterialProperty(NapiApi::FunctionContext<> & ctx)483 napi_value MaterialJS::GetMaterialProperty(NapiApi::FunctionContext<>& ctx)
484 {
485 if (!validateSceneRef()) {
486 return ctx.GetUndefined();
487 }
488 auto material = ctx.This().GetNative<SCENE_NS::IMaterial>();
489 if (!material || !material->Textures()) {
490 return ctx.GetUndefined();
491 }
492 SCENE_NS::ITexture::Ptr texture = material->Textures()->GetValueAt(Index);
493 if (!texture) {
494 return ctx.GetUndefined();
495 }
496 napi_value args[] = { ctx.This().ToNapiValue() };
497 return CreateFromNativeInstance(ctx.GetEnv(), texture, PtrType::STRONG, args).ToNapiValue();
498 }
499 template<size_t Index>
SetMaterialProperty(NapiApi::FunctionContext<NapiApi::Object> & ctx)500 void MaterialJS::SetMaterialProperty(NapiApi::FunctionContext<NapiApi::Object>& ctx)
501 {
502 if (!validateSceneRef()) {
503 return;
504 }
505 auto material = ctx.This().GetNative<SCENE_NS::IMaterial>();
506 if (!material || !material->Textures()) {
507 return;
508 }
509 SCENE_NS::ITexture::Ptr texture = material->Textures()->GetValueAt(Index);
510 if (!texture) {
511 return;
512 }
513
514 NapiApi::Object arg = ctx.Arg<0>();
515 if (auto etex = GetNativeObject<SCENE_NS::ITexture>()) {
516 // Copy the exposed data from the given texture
517 META_NS::SetValue(texture->Image(), META_NS::GetValue(etex->Image()));
518 META_NS::SetValue(texture->Factor(), META_NS::GetValue(etex->Factor()));
519 }
520 }
521