• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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