/* * Copyright (C) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "PostProcJS.h" #include #include #include #include #include #include #include #include #include "BloomJS.h" #include "CameraJS.h" using IntfPtr = BASE_NS::shared_ptr; using IntfWeakPtr = BASE_NS::weak_ptr; using namespace SCENE_NS; void PostProcJS::Init(napi_env env, napi_value exports) { using namespace NapiApi; BASE_NS::vector node_props; // clang-format off node_props.push_back(GetSetProperty("bloom")); node_props.emplace_back( GetSetProperty("toneMapping")); node_props.push_back(MakeTROMethod, PostProcJS, &PostProcJS::Dispose>("destroy")); // clang-format on napi_value func; auto status = napi_define_class(env, "PostProcessSettings", NAPI_AUTO_LENGTH, BaseObject::ctor(), nullptr, node_props.size(), node_props.data(), &func); NapiApi::MyInstanceState* mis; GetInstanceData(env, (void**)&mis); mis->StoreCtor("PostProcessSettings", func); } napi_value PostProcJS::Dispose(NapiApi::FunctionContext<>& ctx) { LOG_V("PostProcJS::Dispose"); // see if we have "scenejs" as ext (prefer one given as argument) napi_status stat; CameraJS* ptr { nullptr }; NapiApi::FunctionContext args(ctx); if (args) { if (NapiApi::Object obj = args.Arg<0>()) { if (napi_value ext = obj.Get("CameraJS")) { stat = napi_get_value_external(ctx.GetEnv(), ext, (void**)&ptr); } } } if (!ptr) { NapiApi::Object obj = camera_.GetObject(); if (obj) { auto* tro = obj.Native(); if (tro) { ptr = static_cast(tro->GetInstanceImpl(CameraJS::ID)); } } } DisposeNative(ptr); return {}; } void PostProcJS::DisposeNative(void* cam) { if (cam == nullptr) { if (!disposed_) { LOG_F("PostProcJS::DisposeNative but argument NULL"); } return; } if (!disposed_) { disposed_ = true; LOG_V("PostProcJS::DisposeNative"); // make sure we release toneMap settings if (auto tmjs = toneMap_.GetObject()) { NapiApi::Function func = tmjs.Get("destroy"); if (func) { func.Invoke(tmjs); } } toneMap_.Reset(); if (auto bmjs = bloom_.GetObject()) { NapiApi::Function func = bmjs.Get("destroy"); if (func) { func.Invoke(bmjs); } } bloom_.Reset(); if (auto post = interface_pointer_cast(GetNativeObject())) { // reset the native object refs SetNativeObject(nullptr, false); SetNativeObject(nullptr, true); post->Tonemap()->SetValue(nullptr); auto cameraJS = camera_.GetObject(); if (cameraJS) { auto* rootobject = cameraJS.Native(); CameraJS* cam = static_cast(rootobject); ExecSyncTask([cam, post = BASE_NS::move(post)]() { cam->ReleaseObject(interface_pointer_cast(post)); return META_NS::IAny::Ptr {}; }); } } } } void* PostProcJS::GetInstanceImpl(uint32_t id) { if (id == PostProcJS::ID) return this; return nullptr; } void PostProcJS::Finalize(napi_env env) { // hmm.. do i need to do something BEFORE the object gets deleted.. DisposeNative(nullptr); BaseObject::Finalize(env); } PostProcJS::PostProcJS(napi_env e, napi_callback_info i) : BaseObject(e, i) { LOG_V("PostProcJS ++"); NapiApi::FunctionContext fromJs(e, i); if (!fromJs) { // no arguments. so internal create. // expecting caller to finish return; } // camera that we bind to.. NapiApi::Object cameraJS = fromJs.Arg<0>(); camera_ = { cameraJS }; auto* rootobject = cameraJS.Native(); if (rootobject == nullptr) { LOG_E("rootobject is nullptr"); return; } auto postproc = interface_pointer_cast( static_cast(rootobject)->CreateObject(SCENE_NS::ClassId::PostProcess)); // create a postprocess object owned by CameraJS. // process constructor args.. NapiApi::Object meJs(fromJs.This()); // weak ref, as we expect to be owned by the camera. SetNativeObject(interface_pointer_cast(postproc), false); StoreJsObj(interface_pointer_cast(postproc), meJs); // now, based on parameters, create correct objects. if (NapiApi::Object args = fromJs.Arg<1>()) { if (auto prm = args.Get("toneMapping")) { // enable tonemap. napi_value innerArgs[] = { meJs.ToNapiValue(), // postprocess prm // tonemap settings }; SCENE_NS::ITonemap::Ptr tone = postproc->Tonemap()->GetValue(); MakeNativeObjectParam(e, tone, BASE_NS::countof(innerArgs), innerArgs); NapiApi::Object tonemapJS( GetJSConstructor(e, "ToneMappingSettings"), BASE_NS::countof(innerArgs), innerArgs); meJs.Set("toneMapping", tonemapJS); } if (auto prm = args.Get("bloom")) { meJs.Set("bloom", prm); } } } PostProcJS::~PostProcJS() { LOG_V("PostProcJS --"); DisposeNative(nullptr); if (!GetNativeObject()) { return; } } void PostProcJS::SetToneMapping(NapiApi::FunctionContext& ctx) { auto postproc = interface_cast(GetNativeObject()); if (!postproc) { // not possible. return; } NapiApi::Object tonemapJS = ctx.Arg<0>(); if (auto currentlySet = toneMap_.GetObject()) { if (tonemapJS.StrictEqual(currentlySet)) { // setting the exactly the same tonemap setting. do nothing. return; } // dispose the old bound object.. (actually should just convert to unbound state. and release the native object, // which may or may not get disposed.) NapiApi::Function func = currentlySet.Get("destroy"); if (func) { func.Invoke(currentlySet); } toneMap_.Reset(); } TrueRootObject* native { nullptr }; SCENE_NS::ITonemap::Ptr tonemap; // does the input parameter already have a bridge.. native = tonemapJS.Native(); if (!native) { // nope.. so create a new bridge object based on the input. napi_value args[] = { ctx.This().ToNapiValue(), // postproc.. ctx.Arg<0>().ToNapiValue() // "javascript object for values" }; NapiApi::Object res(GetJSConstructor(ctx.Env(), "ToneMappingSettings"), BASE_NS::countof(args), args); native = res.Native(); tonemapJS = res; // creating a new js object does have the side effect of not syncing modifications to input object to affect. // you always need to GET a bound one from post proc to control it.. // // we COULD forcefully bind to the input object in this case. // but not sure what possible side effects would that have later on. } else { tonemap = interface_pointer_cast(native->GetNativeObject()); postproc->Tonemap()->SetValue(tonemap); } toneMap_ = NapiApi::StrongRef(tonemapJS); // take ownership of the object. } napi_value PostProcJS::GetToneMapping(NapiApi::FunctionContext<>& ctx) { if (auto postproc = interface_cast(GetNativeObject())) { SCENE_NS::ITonemap::Ptr tone = META_NS::GetValue(postproc->Tonemap()); if ((!tone) || (!META_NS::GetValue(tone->Enabled(), false))) { // no tonemap object or tonemap disabled. return ctx.GetUndefined(); } auto obj = interface_pointer_cast(tone); if (auto cached = FetchJsObj(obj)) { // always return the same js object. return cached.ToNapiValue(); } napi_value args[] = { ctx.This().ToNapiValue() // postproc.. }; MakeNativeObjectParam(ctx.GetEnv(), tone, BASE_NS::countof(args), args); auto tonemapJS = CreateFromNativeInstance(ctx.GetEnv(), obj, false, BASE_NS::countof(args), args); toneMap_ = NapiApi::StrongRef(tonemapJS); // take ownership of the object. return tonemapJS.ToNapiValue(); } toneMap_.Reset(); return ctx.GetUndefined(); } napi_value PostProcJS::GetBloom(NapiApi::FunctionContext<>& ctx) { if (!bloom_.IsEmpty()) { // okay return the existing one. return bloom_.GetValue(); } BloomConfiguration* data = nullptr; if (auto postproc = interface_pointer_cast(GetNativeObject())) { auto bloom = postproc->Bloom()->GetValue(); if (bloom->Enabled()->GetValue()) { data = new BloomConfiguration(); data->SetFrom(bloom); data->SetPostProc(postproc); } } if (data == nullptr) { return ctx.GetUndefined(); } NapiApi::Env env(ctx.GetEnv()); NapiApi::Object obj(env); bloom_ = BASE_NS::move(data->Wrap(obj)); return bloom_.GetValue(); } void PostProcJS::SetBloom(NapiApi::FunctionContext& ctx) { auto postproc = interface_pointer_cast(GetNativeObject()); if (!postproc) { return; } BloomConfiguration* settings = nullptr; NapiApi::Env env(ctx.GetEnv()); NapiApi::Object inputObj = ctx.Arg<0>(); bool enabled = false; if (inputObj) { enabled = true; settings = BloomConfiguration::Unwrap(inputObj); // is it wrapped? if (settings) { auto boundpost = settings->GetPostProc(); if ((boundpost) && (boundpost != postproc)) { // silently fail, we can not use settings object from another postprocess object yet. LOG_F("Tried to attach a bloom setting object that was already bound to another one"); } else { // has wrapper, but no native postproc. (or the bound one is the same) // so we can use it. settings->SetPostProc(postproc); // save the reference.. bloom_ = BASE_NS::move(NapiApi::StrongRef(inputObj)); } } else { settings = new BloomConfiguration(); settings->SetFrom(inputObj); settings->SetPostProc(postproc); bloom_ = settings->Wrap(inputObj); } } else { // disabling bloom.. get current, and unwrap it. auto oldObj = bloom_.GetObject(); settings = BloomConfiguration::Unwrap(oldObj); if (settings) { // detaches native and javascript object. settings->SetPostProc(nullptr); // detach from object } // release our reference to the current bloom settings (JS) bloom_.Reset(); enabled = false; } if (SCENE_NS::IBloom::Ptr bloom = postproc->Bloom()->GetValue()) { bloom->Enabled()->SetValue(enabled); postproc->Bloom()->SetValue(bloom); } }