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
16 #include "PostProcJS.h"
17
18 #include <meta/api/make_callback.h>
19 #include <meta/api/util.h>
20 #include <meta/interface/intf_task_queue.h>
21 #include <meta/interface/intf_task_queue_registry.h>
22 #include <meta/interface/property/property_events.h>
23 #include <scene/interface/intf_node.h>
24 #include <scene/interface/intf_scene.h>
25
26 #include <render/intf_render_context.h>
27
28 #include "BloomJS.h"
29 #include "CameraJS.h"
30 using IntfPtr = BASE_NS::shared_ptr<CORE_NS::IInterface>;
31 using IntfWeakPtr = BASE_NS::weak_ptr<CORE_NS::IInterface>;
32 using namespace SCENE_NS;
33
Init(napi_env env,napi_value exports)34 void PostProcJS::Init(napi_env env, napi_value exports)
35 {
36 using namespace NapiApi;
37
38 BASE_NS::vector<napi_property_descriptor> node_props;
39 // clang-format off
40
41 node_props.push_back(GetSetProperty<Object, PostProcJS, &PostProcJS::GetBloom, &PostProcJS::SetBloom>("bloom"));
42 node_props.emplace_back(
43 GetSetProperty<Object, PostProcJS, &PostProcJS::GetToneMapping, &PostProcJS::SetToneMapping>("toneMapping"));
44 node_props.push_back(MakeTROMethod<NapiApi::FunctionContext<>, PostProcJS, &PostProcJS::Dispose>("destroy"));
45
46 // clang-format on
47
48 napi_value func;
49 auto status = napi_define_class(env, "PostProcessSettings", NAPI_AUTO_LENGTH, BaseObject::ctor<PostProcJS>(),
50 nullptr, node_props.size(), node_props.data(), &func);
51
52 NapiApi::MyInstanceState* mis;
53 GetInstanceData(env, (void**)&mis);
54 mis->StoreCtor("PostProcessSettings", func);
55 }
56
Dispose(NapiApi::FunctionContext<> & ctx)57 napi_value PostProcJS::Dispose(NapiApi::FunctionContext<>& ctx)
58 {
59 LOG_V("PostProcJS::Dispose");
60 // see if we have "scenejs" as ext (prefer one given as argument)
61 napi_status stat;
62 CameraJS* ptr { nullptr };
63 NapiApi::FunctionContext<NapiApi::Object> args(ctx);
64 if (args) {
65 if (NapiApi::Object obj = args.Arg<0>()) {
66 if (napi_value ext = obj.Get("CameraJS")) {
67 stat = napi_get_value_external(ctx.GetEnv(), ext, (void**)&ptr);
68 }
69 }
70 }
71 if (!ptr) {
72 NapiApi::Object obj = camera_.GetObject();
73 if (obj) {
74 auto* tro = obj.Native<TrueRootObject>();
75 if (tro) {
76 ptr = static_cast<CameraJS*>(tro->GetInstanceImpl(CameraJS::ID));
77 }
78 }
79 }
80
81 DisposeNative(ptr);
82 return {};
83 }
DisposeNative(void * cam)84 void PostProcJS::DisposeNative(void* cam)
85 {
86 if (cam == nullptr) {
87 if (!disposed_) {
88 LOG_F("PostProcJS::DisposeNative but argument NULL");
89 }
90 return;
91 }
92 if (!disposed_) {
93 disposed_ = true;
94 LOG_V("PostProcJS::DisposeNative");
95 // make sure we release toneMap settings
96
97 if (auto tmjs = toneMap_.GetObject()) {
98 NapiApi::Function func = tmjs.Get<NapiApi::Function>("destroy");
99 if (func) {
100 func.Invoke(tmjs);
101 }
102 }
103 toneMap_.Reset();
104
105 if (auto bmjs = bloom_.GetObject()) {
106 NapiApi::Function func = bmjs.Get<NapiApi::Function>("destroy");
107 if (func) {
108 func.Invoke(bmjs);
109 }
110 }
111 bloom_.Reset();
112 if (auto post = interface_pointer_cast<IPostProcess>(GetNativeObject())) {
113 // reset the native object refs
114 SetNativeObject(nullptr, false);
115 SetNativeObject(nullptr, true);
116 post->Tonemap()->SetValue(nullptr);
117 auto cameraJS = camera_.GetObject();
118 if (cameraJS) {
119 auto* rootobject = cameraJS.Native<TrueRootObject>();
120 CameraJS* cam = static_cast<CameraJS*>(rootobject);
121
122 ExecSyncTask([cam, post = BASE_NS::move(post)]() {
123 cam->ReleaseObject(interface_pointer_cast<META_NS::IObject>(post));
124 return META_NS::IAny::Ptr {};
125 });
126 }
127 }
128 }
129 }
GetInstanceImpl(uint32_t id)130 void* PostProcJS::GetInstanceImpl(uint32_t id)
131 {
132 if (id == PostProcJS::ID)
133 return this;
134 return nullptr;
135 }
Finalize(napi_env env)136 void PostProcJS::Finalize(napi_env env)
137 {
138 // hmm.. do i need to do something BEFORE the object gets deleted..
139 DisposeNative(nullptr);
140 BaseObject<PostProcJS>::Finalize(env);
141 }
142
PostProcJS(napi_env e,napi_callback_info i)143 PostProcJS::PostProcJS(napi_env e, napi_callback_info i) : BaseObject<PostProcJS>(e, i)
144 {
145 LOG_V("PostProcJS ++");
146 NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object> fromJs(e, i);
147 if (!fromJs) {
148 // no arguments. so internal create.
149 // expecting caller to finish
150 return;
151 }
152
153 // camera that we bind to..
154 NapiApi::Object cameraJS = fromJs.Arg<0>();
155 camera_ = { cameraJS };
156 auto* rootobject = cameraJS.Native<TrueRootObject>();
157 if (rootobject == nullptr) {
158 LOG_E("rootobject is nullptr");
159 return;
160 }
161 auto postproc = interface_pointer_cast<SCENE_NS::IPostProcess>(
162 static_cast<CameraJS*>(rootobject)->CreateObject(SCENE_NS::ClassId::PostProcess));
163
164 // create a postprocess object owned by CameraJS.
165
166 // process constructor args..
167 NapiApi::Object meJs(fromJs.This());
168 // weak ref, as we expect to be owned by the camera.
169 SetNativeObject(interface_pointer_cast<META_NS::IObject>(postproc), false);
170 StoreJsObj(interface_pointer_cast<META_NS::IObject>(postproc), meJs);
171 // now, based on parameters, create correct objects.
172 if (NapiApi::Object args = fromJs.Arg<1>()) {
173 if (auto prm = args.Get("toneMapping")) {
174 // enable tonemap.
175 napi_value innerArgs[] = {
176 meJs.ToNapiValue(), // postprocess
177 prm // tonemap settings
178 };
179 SCENE_NS::ITonemap::Ptr tone = postproc->Tonemap()->GetValue();
180 MakeNativeObjectParam(e, tone, BASE_NS::countof(innerArgs), innerArgs);
181 NapiApi::Object tonemapJS(
182 GetJSConstructor(e, "ToneMappingSettings"), BASE_NS::countof(innerArgs), innerArgs);
183 meJs.Set("toneMapping", tonemapJS);
184 }
185 if (auto prm = args.Get("bloom")) {
186 meJs.Set("bloom", prm);
187 }
188 }
189 }
190
~PostProcJS()191 PostProcJS::~PostProcJS()
192 {
193 LOG_V("PostProcJS --");
194 DisposeNative(nullptr);
195 if (!GetNativeObject()) {
196 return;
197 }
198 }
199
SetToneMapping(NapiApi::FunctionContext<NapiApi::Object> & ctx)200 void PostProcJS::SetToneMapping(NapiApi::FunctionContext<NapiApi::Object>& ctx)
201 {
202 auto postproc = interface_cast<SCENE_NS::IPostProcess>(GetNativeObject());
203 if (!postproc) {
204 // not possible.
205 return;
206 }
207 NapiApi::Object tonemapJS = ctx.Arg<0>();
208
209 if (auto currentlySet = toneMap_.GetObject()) {
210 if (tonemapJS.StrictEqual(currentlySet)) { // setting the exactly the same tonemap setting. do nothing.
211 return;
212 }
213 // dispose the old bound object.. (actually should just convert to unbound state. and release the native object,
214 // which may or may not get disposed.)
215 NapiApi::Function func = currentlySet.Get<NapiApi::Function>("destroy");
216 if (func) {
217 func.Invoke(currentlySet);
218 }
219 toneMap_.Reset();
220 }
221
222 TrueRootObject* native { nullptr };
223 SCENE_NS::ITonemap::Ptr tonemap;
224 // does the input parameter already have a bridge..
225 native = tonemapJS.Native<TrueRootObject>();
226 if (!native) {
227 // nope.. so create a new bridge object based on the input.
228 napi_value args[] = {
229 ctx.This().ToNapiValue(), // postproc..
230 ctx.Arg<0>().ToNapiValue() // "javascript object for values"
231 };
232 NapiApi::Object res(GetJSConstructor(ctx.Env(), "ToneMappingSettings"), BASE_NS::countof(args), args);
233 native = res.Native<TrueRootObject>();
234 tonemapJS = res;
235
236 // creating a new js object does have the side effect of not syncing modifications to input object to affect.
237 // you always need to GET a bound one from post proc to control it..
238 //
239 // we COULD forcefully bind to the input object in this case.
240 // but not sure what possible side effects would that have later on.
241 } else {
242 tonemap = interface_pointer_cast<SCENE_NS::ITonemap>(native->GetNativeObject());
243 postproc->Tonemap()->SetValue(tonemap);
244 }
245 toneMap_ = NapiApi::StrongRef(tonemapJS); // take ownership of the object.
246 }
247
GetToneMapping(NapiApi::FunctionContext<> & ctx)248 napi_value PostProcJS::GetToneMapping(NapiApi::FunctionContext<>& ctx)
249 {
250 if (auto postproc = interface_cast<SCENE_NS::IPostProcess>(GetNativeObject())) {
251 SCENE_NS::ITonemap::Ptr tone = META_NS::GetValue(postproc->Tonemap());
252 if ((!tone) || (!META_NS::GetValue(tone->Enabled(), false))) {
253 // no tonemap object or tonemap disabled.
254 return ctx.GetUndefined();
255 }
256 auto obj = interface_pointer_cast<META_NS::IObject>(tone);
257
258 if (auto cached = FetchJsObj(obj)) {
259 // always return the same js object.
260 return cached.ToNapiValue();
261 }
262
263 napi_value args[] = {
264 ctx.This().ToNapiValue() // postproc..
265 };
266 MakeNativeObjectParam(ctx.GetEnv(), tone, BASE_NS::countof(args), args);
267 auto tonemapJS = CreateFromNativeInstance(ctx.GetEnv(), obj, false, BASE_NS::countof(args), args);
268 toneMap_ = NapiApi::StrongRef(tonemapJS); // take ownership of the object.
269 return tonemapJS.ToNapiValue();
270 }
271 toneMap_.Reset();
272 return ctx.GetUndefined();
273 }
GetBloom(NapiApi::FunctionContext<> & ctx)274 napi_value PostProcJS::GetBloom(NapiApi::FunctionContext<>& ctx)
275 {
276 if (!bloom_.IsEmpty()) {
277 // okay return the existing one.
278 return bloom_.GetValue();
279 }
280
281 BloomConfiguration* data = nullptr;
282 if (auto postproc = interface_pointer_cast<SCENE_NS::IPostProcess>(GetNativeObject())) {
283 auto bloom = postproc->Bloom()->GetValue();
284 if (bloom->Enabled()->GetValue()) {
285 data = new BloomConfiguration();
286 data->SetFrom(bloom);
287 data->SetPostProc(postproc);
288 }
289 }
290
291 if (data == nullptr) {
292 return ctx.GetUndefined();
293 }
294
295 NapiApi::Env env(ctx.GetEnv());
296 NapiApi::Object obj(env);
297 bloom_ = BASE_NS::move(data->Wrap(obj));
298 return bloom_.GetValue();
299 }
300
SetBloom(NapiApi::FunctionContext<NapiApi::Object> & ctx)301 void PostProcJS::SetBloom(NapiApi::FunctionContext<NapiApi::Object>& ctx)
302 {
303 auto postproc = interface_pointer_cast<SCENE_NS::IPostProcess>(GetNativeObject());
304 if (!postproc) {
305 return;
306 }
307 BloomConfiguration* settings = nullptr;
308 NapiApi::Env env(ctx.GetEnv());
309 NapiApi::Object inputObj = ctx.Arg<0>();
310 bool enabled = false;
311 if (inputObj) {
312 enabled = true;
313 settings = BloomConfiguration::Unwrap(inputObj);
314 // is it wrapped?
315 if (settings) {
316 auto boundpost = settings->GetPostProc();
317 if ((boundpost) && (boundpost != postproc)) {
318 // silently fail, we can not use settings object from another postprocess object yet.
319 LOG_F("Tried to attach a bloom setting object that was already bound to another one");
320 } else {
321 // has wrapper, but no native postproc. (or the bound one is the same)
322 // so we can use it.
323 settings->SetPostProc(postproc);
324 // save the reference..
325 bloom_ = BASE_NS::move(NapiApi::StrongRef(inputObj));
326 }
327 } else {
328 settings = new BloomConfiguration();
329 settings->SetFrom(inputObj);
330 settings->SetPostProc(postproc);
331 bloom_ = settings->Wrap(inputObj);
332 }
333 } else {
334 // disabling bloom.. get current, and unwrap it.
335 auto oldObj = bloom_.GetObject();
336 settings = BloomConfiguration::Unwrap(oldObj);
337 if (settings) {
338 // detaches native and javascript object.
339 settings->SetPostProc(nullptr); // detach from object
340 }
341 // release our reference to the current bloom settings (JS)
342 bloom_.Reset();
343 enabled = false;
344 }
345 if (SCENE_NS::IBloom::Ptr bloom = postproc->Bloom()->GetValue()) {
346 bloom->Enabled()->SetValue(enabled);
347 postproc->Bloom()->SetValue(bloom);
348 }
349 }
350