/*
* 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"
#include "ToneMapJS.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;
NapiApi::MyInstanceState::GetInstance(env, (void**)&mis);
if (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) {
ptr = camera_.GetObject().GetJsWrapper();
}
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
// Detach this wrapper and its members tonemap and bloom from the native objects.
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())) {
UnsetNativeObject();
if (const auto cam = camera_.GetObject().GetJsWrapper()) {
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)
{
DisposeNative(camera_.GetObject().GetJsWrapper());
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 postproc = GetNativeObject();
if (!postproc) {
LOG_E("Error creating PostProcessSettings: No native object given");
assert(false);
return;
}
// process constructor args..
NapiApi::Object meJs(fromJs.This());
// 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[] = { prm };
const auto tone = postproc->Tonemap()->GetValue();
// The tonemap is a part of the PostProcess object, so we own it. TonemapJS get only a weak ref.
const auto tonemapJS = CreateFromNativeInstance(e, tone, PtrType::WEAK, 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)) {
return;
}
if (const auto wrapper = currentlySet.GetJsWrapper()) {
// Free the old tonemap so it isn't bound to the changes happening in the new one.
wrapper->UnbindFromNative();
}
}
auto target = postproc->Tonemap()->GetValue();
if (!target) {
return;
}
// If the new JS tonemap is bound to a native tonemap, we have this problem:
// Should set the tonemap of both cameras but won't.
auto source = tonemapJS.GetNative();
if (tonemapJS.IsNull()) {
META_NS::SetValue(target->Enabled(), false);
} else {
const auto newType = tonemapJS.Get("type").valueOrDefault(ToneMapJS::DEFAULT_TYPE);
const auto newExposure = tonemapJS.Get("exposure").valueOrDefault(ToneMapJS::DEFAULT_EXPOSURE);
META_NS::SetValue(target->Type(), ToneMapJS::ToNativeType(newType));
META_NS::SetValue(target->Exposure(), newExposure);
META_NS::SetValue(target->Enabled(), true);
}
toneMap_ = NapiApi::StrongRef(tonemapJS);
}
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 tonemapJS = CreateFromNativeInstance(ctx.GetEnv(), tone, PtrType::WEAK, {});
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 = GetNativeObject();
if (!postproc) {
return;
}
bool enabled = false;
if (const auto bloomJs = NapiApi::Object { ctx.Arg<0>() }) {
enabled = true;
// is it wrapped?
if (auto wrapper = BloomConfiguration::Unwrap(bloomJs)) {
if (const auto newPostProc = wrapper->GetPostProc(); newPostProc != postproc) {
// Can't use the wrapper of another postprocess object yet.
LOG_F("Tried to attach a bloom wrapper object that was already bound to another one");
return;
}
// The new bloom is wrapped, but has no native postproc (or we are already its native).
wrapper->SetPostProc(postproc);
bloom_ = NapiApi::StrongRef(bloomJs);
} else {
wrapper = new BloomConfiguration();
wrapper->SetFrom(bloomJs);
wrapper->SetPostProc(postproc);
bloom_ = wrapper->Wrap(bloomJs);
}
} else {
// disabling bloom.. get current, and unwrap it.
auto oldBloom = bloom_.GetObject();
if (auto oldWrapper = BloomConfiguration::Unwrap(oldBloom)) {
// detaches native and javascript object.
oldWrapper->SetPostProc(nullptr); // detach from object
}
// release our reference to the current bloom settings (JS)
bloom_.Reset();
}
if (auto bloom = postproc->Bloom()->GetValue()) {
bloom->Enabled()->SetValue(enabled);
// Poke postProc so that the changes to bloom are updated.
}
}