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 "ShaderJS.h"
16 
17 #include <scene_plugin/api/material_uid.h>
18 #include <scene_plugin/interface/intf_ecs_object.h>
19 #include <scene_plugin/interface/intf_mesh.h>
20 #include <scene_plugin/interface/intf_scene.h>
21 
22 #include "MaterialJS.h"
23 #include "SceneJS.h"
24 #include "Vec2Proxy.h"
25 #include "Vec3Proxy.h"
26 #include "Vec4Proxy.h"
27 
28 using IntfPtr = META_NS::SharedPtrIInterface;
29 using IntfWeakPtr = META_NS::WeakPtrIInterface;
30 
31 template<typename type>
32 class TypeProxy : public Proxy {
33     META_NS::IProperty::Ptr prop_;
34     BASE_NS::string name_;
35 
36 public:
TypeProxy(META_NS::IProperty::Ptr prop)37     explicit TypeProxy(META_NS::IProperty::Ptr prop) : prop_(prop), name_(prop_->GetName()) {}
~TypeProxy()38     ~TypeProxy()
39     {
40         prop_.reset();
41     }
42 
Get(NapiApi::FunctionContext<> ctx)43     napi_value Get(NapiApi::FunctionContext<> ctx)
44     {
45         META_NS::Property<type> p(prop_);
46         return NapiApi::Value<type>(ctx, p->GetValue());
47     }
Set(NapiApi::FunctionContext<type> ctx)48     napi_value Set(NapiApi::FunctionContext<type> ctx)
49     {
50         type val = ctx.template Arg<0>();
51         META_NS::Property<type> p(prop_);
52         p->SetValue(val);
53         return ctx.GetUndefined();
54     }
insertProp(BASE_NS::vector<napi_property_descriptor> & props)55     void insertProp(BASE_NS::vector<napi_property_descriptor>& props)
56     {
57         props.push_back(napi_property_descriptor { name_.c_str(), nullptr, nullptr,
58             [](napi_env e, napi_callback_info i) -> napi_value {
59                 NapiApi::FunctionContext<> info(e, i);
60                 auto me_ = (TypeProxy<type>*)info.GetData();
61                 return me_->Get(info);
62             },
63             [](napi_env e, napi_callback_info i) -> napi_value {
64                 NapiApi::FunctionContext<type> info(e, i);
65                 auto me_ = (TypeProxy<type>*)info.GetData();
66                 return me_->Set(info);
67             },
68             nullptr, napi_default_jsproperty, (void*)this });
69     }
70 };
71 
72 class BitmapProxy : public Proxy {
73     NapiApi::StrongRef scene_;
74     META_NS::IProperty::Ptr prop_;
75     BASE_NS::string name_;
76 
77 public:
BitmapProxy(NapiApi::Object scene,META_NS::IProperty::Ptr prop,BASE_NS::string_view prefix="")78     BitmapProxy(NapiApi::Object scene, META_NS::IProperty::Ptr prop, BASE_NS::string_view prefix = "") : prop_(prop)
79     {
80         scene_ = scene;
81         if (!prefix.empty()) {
82             name_ = prefix;
83             name_ += "_";
84         }
85         name_ += prop->GetName();
86     }
~BitmapProxy()87     ~BitmapProxy()
88     {
89         prop_.reset();
90     }
91 
Get(NapiApi::FunctionContext<> ctx)92     napi_value Get(NapiApi::FunctionContext<> ctx)
93     {
94         META_NS::Property<SCENE_NS::IBitmap::Ptr> p(prop_);
95         // return NapiApi::Value<type>(ctx, p->GetValue());
96         auto obj = p->GetValue();
97         if (auto cached = FetchJsObj(obj)) {
98             return cached;
99         }
100 
101         if (obj) {
102             // okay.. there is a native bitmap set.. but no js wrapper yet.
103 
104             // create the jsobject if we don't have one.
105             NapiApi::Object parms(ctx);
106             napi_value args[] = {
107                 scene_.GetValue(), // scene..
108                 parms              // params.
109             };
110             MakeNativeObjectParam(ctx, obj, BASE_NS::countof(args), args);
111 
112             auto size = obj->Size()->GetValue();
113             auto uri = obj->Uri()->GetValue();
114             auto name = interface_cast<META_NS::INamed>(obj)->Name()->GetValue();
115             parms.Set("uri", uri);
116             NapiApi::Object imageJS(GetJSConstructor(ctx, "Image"), BASE_NS::countof(args), args);
117             return imageJS;
118         }
119         return ctx.GetNull();
120     }
Set(NapiApi::FunctionContext<NapiApi::Object> ctx)121     napi_value Set(NapiApi::FunctionContext<NapiApi::Object> ctx)
122     {
123         NapiApi::Object val = ctx.Arg<0>();
124         auto bitmap = GetNativeMeta<SCENE_NS::IBitmap>(val);
125         if (bitmap) {
126             META_NS::Property<SCENE_NS::IBitmap::Ptr> p(prop_);
127             p->SetValue(bitmap);
128         }
129         return ctx.GetUndefined();
130     }
insertProp(BASE_NS::vector<napi_property_descriptor> & props)131     void insertProp(BASE_NS::vector<napi_property_descriptor>& props)
132     {
133         props.push_back(napi_property_descriptor { name_.c_str(), nullptr, nullptr,
134             [](napi_env e, napi_callback_info i) -> napi_value {
135                 NapiApi::FunctionContext<> info(e, i);
136                 auto me_ = (BitmapProxy*)info.GetData();
137                 return me_->Get(info);
138             },
139             [](napi_env e, napi_callback_info i) -> napi_value {
140                 NapiApi::FunctionContext<NapiApi::Object> info(e, i);
141                 auto me_ = (BitmapProxy*)info.GetData();
142                 return me_->Set(info);
143             },
144             nullptr, napi_default_jsproperty, (void*)this });
145     }
146 };
147 
148 template<typename proxType>
149 class PropProxy : public Proxy {
150     BASE_NS::shared_ptr<proxType> proxy_;
151     BASE_NS::string name_;
152 
153 public:
PropProxy(napi_env e,META_NS::IProperty::Ptr prop,BASE_NS::string_view prefix="")154     PropProxy(napi_env e, META_NS::IProperty::Ptr prop, BASE_NS::string_view prefix = "")
155     {
156         proxy_.reset(new proxType(e, prop));
157         if (!prefix.empty()) {
158             name_ = prefix;
159             name_ += "_";
160         }
161         name_ += prop->GetName();
162     }
~PropProxy()163     ~PropProxy()
164     {
165         proxy_.reset();
166     }
Get(NapiApi::FunctionContext<> ctx)167     napi_value Get(NapiApi::FunctionContext<> ctx)
168     {
169         return proxy_->Value();
170     }
171 
Set(NapiApi::FunctionContext<NapiApi::Object> ctx)172     napi_value Set(NapiApi::FunctionContext<NapiApi::Object> ctx)
173     {
174         NapiApi::Object val = ctx.Arg<0>();
175         proxy_->SetValue(val);
176         return ctx.GetUndefined();
177     }
178 
insertProp(BASE_NS::vector<napi_property_descriptor> & props)179     void insertProp(BASE_NS::vector<napi_property_descriptor>& props)
180     {
181         props.push_back(napi_property_descriptor { name_.c_str(), nullptr, nullptr,
182             [](napi_env e, napi_callback_info i) -> napi_value {
183                 NapiApi::FunctionContext<> info(e, i);
184                 auto me_ = (PropProxy<proxType>*)info.GetData();
185                 return me_->Get(info);
186             },
187             [](napi_env e, napi_callback_info i) -> napi_value {
188                 NapiApi::FunctionContext<NapiApi::Object> info(e, i);
189                 auto me_ = (PropProxy<proxType>*)info.GetData();
190                 return me_->Set(info);
191             },
192             nullptr, napi_default_jsproperty, (void*)this });
193     }
194 };
195 
Init(napi_env env,napi_value exports)196 void ShaderJS::Init(napi_env env, napi_value exports)
197 {
198     using namespace NapiApi;
199     BASE_NS::vector<napi_property_descriptor> node_props;
200 
201     SceneResourceImpl::GetPropertyDescs(node_props);
202 
203     napi_value func;
204     auto status = napi_define_class(env, "Shader", NAPI_AUTO_LENGTH, BaseObject::ctor<ShaderJS>(), nullptr,
205         node_props.size(), node_props.data(), &func);
206 
207     NapiApi::MyInstanceState* mis;
208     napi_get_instance_data(env, (void**)&mis);
209     mis->StoreCtor("Shader", func);
210 }
211 
ShaderJS(napi_env e,napi_callback_info i)212 ShaderJS::ShaderJS(napi_env e, napi_callback_info i)
213     : BaseObject<ShaderJS>(e, i), SceneResourceImpl(SceneResourceImpl::SHADER)
214 {
215     NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object> fromJs(e, i);
216     NapiApi::Object meJs(e, fromJs.This());
217 
218     NapiApi::Object scene = fromJs.Arg<0>(); // access to owning scene...
219     NapiApi::Object args = fromJs.Arg<1>();  // other args
220     scene_ = { scene };
221     if (!GetNativeMeta<SCENE_NS::IScene>(scene_.GetObject())) {
222         CORE_LOG_F("INVALID SCENE!");
223     }
224 
225     auto* tro = scene.Native<TrueRootObject>();
226     auto* sceneJS = ((SceneJS*)tro->GetInstanceImpl(SceneJS::ID));
227     sceneJS->DisposeHook((uintptr_t)&scene_, meJs);
228 
229     // check if we got the NativeObject as parameter. (meta object created when bound to material..)
230     auto metaobj = GetNativeObjectParam<META_NS::IMetadata>(args);
231 
232     StoreJsObj(interface_pointer_cast<META_NS::IObject>(metaobj), meJs);
233 
234     // check if it's a SCENE_NS::IShader (can only be set on instances created createShader, these are the "template"
235     // shaders.)
236     auto shader = interface_pointer_cast<SCENE_NS::IShader>(metaobj);
237     if (shader) {
238         // we should not be bound to a material then.. this is a place holder object.
239         SetNativeObject(interface_pointer_cast<META_NS::IObject>(shader), true);
240     } else {
241         // should be bound to a material..
242         // so the shader should be stored as a parameter..
243         SetNativeObject(interface_pointer_cast<META_NS::IObject>(metaobj), true);
244         shader = interface_pointer_cast<SCENE_NS::IShader>(metaobj->GetPropertyByName<IntfPtr>("shader")->GetValue());
245     }
246 
247     NapiApi::Object material = args.Get<NapiApi::Object>("Material"); // see if we SHOULD be bound to a material.
248     if (material) {
249         BindToMaterial(meJs, material);
250     }
251 
252     BASE_NS::string name;
253     if (auto prm = args.Get<BASE_NS::string>("name")) {
254         name = prm;
255     } else {
256         if (auto named = interface_cast<META_NS::INamed>(metaobj)) {
257             name = named->Name()->GetValue();
258         }
259     }
260     meJs.Set("name", name);
261 }
262 
BindToMaterial(NapiApi::Object meJs,NapiApi::Object material)263 void ShaderJS::BindToMaterial(NapiApi::Object meJs, NapiApi::Object material)
264 {
265     auto metaobj = GetNativeMeta<META_NS::IMetadata>(meJs);
266     auto shader = interface_pointer_cast<SCENE_NS::IShader>(
267         metaobj->GetPropertyByName<IntfPtr>("shader")->GetValue());
268 
269     napi_env e = meJs.GetEnv();
270     // inputs are actually owned (and used) by the material.
271     // create the input object
272     NapiApi::Object inputs(e);
273 
274     auto* tro = material.Native<TrueRootObject>();
275     auto mat = interface_pointer_cast<SCENE_NS::IMaterial>(tro->GetNativeObject());
276 
277     BASE_NS::vector<napi_property_descriptor> inputProps;
278 
279     META_NS::IMetadata::Ptr customProperties;
280     BASE_NS::vector<SCENE_NS::ITextureInfo::Ptr> Textures;
281 
282     ExecSyncTask([mat, &customProperties, &Textures]() {
283         Textures = mat->Inputs()->GetValue();
284         customProperties = interface_pointer_cast<META_NS::IMetadata>(mat->CustomProperties()->GetValue());
285         return META_NS::IAny::Ptr {};
286     });
287     if (!Textures.empty()) {
288         int index = 0;
289         for (auto t : Textures) {
290             BASE_NS::string name;
291             auto nn = interface_cast<META_NS::INamed>(t);
292             if (nn) {
293                 name = nn->Name()->GetValue();
294             } else {
295                 name = "TextureInfo_" + BASE_NS::to_string(index);
296             }
297             BASE_NS::shared_ptr<Proxy> proxt;
298             // factor
299             proxt = BASE_NS::shared_ptr { new PropProxy<Vec4Proxy>(e, t->Factor(), name) };
300             if (proxt) {
301                 proxies_.push_back(proxt);
302                 proxt->insertProp(inputProps);
303             }
304             proxt = BASE_NS::shared_ptr { new BitmapProxy(scene_.GetObject(), t->Image(), name) };
305             if (proxt) {
306                 proxies_.push_back(proxt);
307                 proxt->insertProp(inputProps);
308             }
309 
310             index++;
311         }
312     }
313     if (customProperties) {
314         for (auto t : customProperties->GetAllProperties()) {
315             auto name = t->GetName();
316             auto type = t->GetTypeId();
317             auto tst = type.ToString();
318             BASE_NS::shared_ptr<Proxy> proxt;
319             if (type == META_NS::UidFromType<float>()) {
320                 proxt = BASE_NS::shared_ptr { new TypeProxy<float>(t) };
321             }
322             if (type == META_NS::UidFromType<int32_t>()) {
323                 proxt = BASE_NS::shared_ptr { new TypeProxy<int32_t>(t) };
324             }
325             if (type == META_NS::UidFromType<uint32_t>()) {
326                 proxt = BASE_NS::shared_ptr { new TypeProxy<uint32_t>(t) };
327             }
328             if (type == META_NS::UidFromType<BASE_NS::Math::Vec2>()) {
329                 proxt = BASE_NS::shared_ptr { new PropProxy<Vec2Proxy>(e, t) };
330             }
331             if (type == META_NS::UidFromType<BASE_NS::Math::Vec3>()) {
332                 proxt = BASE_NS::shared_ptr { new PropProxy<Vec3Proxy>(e, t) };
333             }
334             if (type == META_NS::UidFromType<BASE_NS::Math::Vec4>()) {
335                 proxt = BASE_NS::shared_ptr { new PropProxy<Vec4Proxy>(e, t) };
336             }
337             if (proxt) {
338                 proxies_.push_back(proxt);
339                 proxt->insertProp(inputProps);
340             }
341         }
342     }
343     if (!inputProps.empty()) {
344         napi_define_properties(e, inputs, inputProps.size(), inputProps.data());
345     }
346 
347     inputs_ = { e, inputs };
348     meJs.Set("inputs", inputs_.GetValue());
349 }
350 
~ShaderJS()351 ShaderJS::~ShaderJS()
352 {
353     DisposeNative();
354 }
355 
GetInstanceImpl(uint32_t id)356 void* ShaderJS::GetInstanceImpl(uint32_t id)
357 {
358     if (id == ShaderJS::ID) {
359         return this;
360     }
361     return SceneResourceImpl::GetInstanceImpl(id);
362 }
363 
DisposeNative()364 void ShaderJS::DisposeNative()
365 {
366     if (!disposed_) {
367         disposed_ = true;
368         SetNativeObject(nullptr, false);
369         SetNativeObject(nullptr, true);
370         NapiApi::Object obj = scene_.GetObject();
371         if (obj) {
372             auto* tro = obj.Native<TrueRootObject>();
373 
374             if (tro) {
375                 SceneJS* sceneJS = ((SceneJS*)tro->GetInstanceImpl(SceneJS::ID));
376                 sceneJS->ReleaseDispose((uintptr_t)&scene_);
377             }
378         }
379         proxies_.clear();
380         inputs_.Reset();
381         scene_.Reset();
382     }
383 }
Finalize(napi_env env)384 void ShaderJS::Finalize(napi_env env)
385 {
386     DisposeNative();
387     BaseObject<ShaderJS>::Finalize(env);
388 }
389