/* * 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 "RenderContextJS.h" #include #include #include #include #include #include #include #include #include #include "JsObjectCache.h" #include "ParamParsing.h" #include "Promise.h" #include "nodejstaskqueue.h" static constexpr BASE_NS::Uid IO_QUEUE { "be88e9a0-9cd8-45ab-be48-937953dc258f" }; META_TYPE(BASE_NS::shared_ptr); struct GlobalResources { NapiApi::WeakRef defaultContext; }; static BASE_NS::weak_ptr globalResources; RenderResources::RenderResources(napi_env env) : env_(env) { // Acquire the js task queue. (make sure that as long as we have a scene, the nodejstaskqueue is useable) if (auto jsQueue = interface_cast(GetOrCreateNodeTaskQueue(env))) { jsQueue->Acquire(); } } RenderResources::~RenderResources() { disposeContainer_.DisposeAll(env_); for (auto b : bitmaps_) { b.second.reset(); } // Release the js task queue. auto tq = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP); if (auto p = interface_cast(tq)) { p->Release(); // check if we can safely de-init here also. if (p->IsReleased()) { // destroy and unregister the queue. DeinitNodeTaskQueue(); } } } void RenderResources::DisposeHook(uintptr_t token, NapiApi::Object obj) { disposeContainer_.DisposeHook(token, obj); } void RenderResources::ReleaseDispose(uintptr_t token) { disposeContainer_.ReleaseDispose(token); } void RenderResources::StrongDisposeHook(uintptr_t token, NapiApi::Object obj) { disposeContainer_.StrongDisposeHook(token, obj); } void RenderResources::ReleaseStrongDispose(uintptr_t token) { disposeContainer_.ReleaseStrongDispose(token); } void RenderResources::StoreBitmap(BASE_NS::string_view uri, SCENE_NS::IBitmap::Ptr bitmap) { CORE_NS::UniqueLock lock(mutex_); if (bitmap) { bitmaps_[uri] = bitmap; } else { // setting null. releases. bitmaps_.erase(uri); } } SCENE_NS::IBitmap::Ptr RenderResources::FetchBitmap(BASE_NS::string_view uri) { CORE_NS::UniqueLock lock(mutex_); auto it = bitmaps_.find(uri); if (it != bitmaps_.end()) { return it->second; } return {}; } void RenderContextJS::Init(napi_env env, napi_value exports) { using namespace NapiApi; napi_property_descriptor props[] = { // static methods Method, RenderContextJS, &RenderContextJS::GetResourceFactory>( "getRenderResourceFactory"), Method, RenderContextJS, &RenderContextJS::Dispose>("destroy"), // SceneResourceFactory methods Method, RenderContextJS, &RenderContextJS::LoadPlugin>("loadPlugin"), Method, RenderContextJS, &RenderContextJS::CreateShader>( "createShader"), Method, RenderContextJS, &RenderContextJS::CreateImage>( "createImage"), Method, RenderContextJS, &RenderContextJS::CreateMeshResource>("createMesh"), Method, RenderContextJS, &RenderContextJS::RegisterResourcePath>("registerResourcePath"), }; napi_value func; auto status = napi_define_class(env, "RenderContext", NAPI_AUTO_LENGTH, BaseObject::ctor(), nullptr, sizeof(props) / sizeof(props[0]), props, &func); napi_set_named_property(env, exports, "RenderContext", func); NapiApi::MyInstanceState* mis; NapiApi::MyInstanceState::GetInstance(env, reinterpret_cast(&mis)); if (mis) { mis->StoreCtor("RenderContext", func); } } void RenderContextJS::RegisterEnums(NapiApi::Object exports) {} RenderContextJS::RenderContextJS(napi_env env, napi_callback_info info) : BaseObject(env, info), env_(env), globalResources_(globalResources.lock()) { LOG_V("RenderContextJS ++"); } RenderContextJS::~RenderContextJS() { LOG_V("RenderContextJS --"); } void* RenderContextJS::GetInstanceImpl(uint32_t id) { if (id == RenderContextJS::ID) { return this; } return nullptr; } BASE_NS::shared_ptr RenderContextJS::GetResources() const { if (auto resources = resources_.lock()) { return resources; } BASE_NS::shared_ptr resources(new RenderResources(env_)); resources_ = resources; return resources; } napi_value RenderContextJS::GetDefaultContext(napi_env env) { auto resources = globalResources.lock(); if (!resources) { resources.reset(new GlobalResources); globalResources = resources; } NapiApi::Object context = NapiApi::Object(env, "RenderContext", {}); resources->defaultContext = context; return context.ToNapiValue(); } napi_value RenderContextJS::Dispose(NapiApi::FunctionContext<>& ctx) { return {}; } void RenderContextJS::DisposeNative(void*) {} void RenderContextJS::Finalize(napi_env env) {} napi_value RenderContextJS::GetResourceFactory(NapiApi::FunctionContext<>& ctx) { return ctx.This().ToNapiValue(); } napi_value RenderContextJS::LoadPlugin(NapiApi::FunctionContext& ctx) { auto c = ctx.Arg<0>().valueOrDefault(); if (!BASE_NS::IsUidString(c)) { LOG_E("\"%s\" is not a Uid string", c.c_str()); Promise promise(ctx.GetEnv()); NapiApi::Value result(ctx.GetEnv(), false); promise.Resolve(result.ToNapiValue()); return promise.ToNapiValue(); } LOG_E("Loading plugin: %s", c.c_str()); BASE_NS::Uid uid(*(char(*)[37])c.data()); const auto engineQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD); META_NS::AddFutureTaskOrRunDirectly(engineQ, [uid]() { Core::GetPluginRegister().LoadPlugins({uid}); }); Promise promise(ctx.GetEnv()); NapiApi::Value result(ctx.GetEnv(), true); promise.Resolve(result.ToNapiValue()); return promise.ToNapiValue(); } napi_value RenderContextJS::CreateShader(NapiApi::FunctionContext& ctx) { return ctx.GetUndefined(); } napi_value RenderContextJS::CreateImage(NapiApi::FunctionContext& ctx) { // Create an image in four steps: // 1. Parse args in JS thread (this function body) // 2. Load image data in IO thread // 3. Create GPU resource in engine thread // 4. Settle promise by converting to JS object in JS release thread using namespace RENDER_NS; const auto env = ctx.GetEnv(); auto promise = Promise(env); NapiApi::Object resourceParams = ctx.Arg<0>(); auto uri = ExtractUri(resourceParams.Get("uri")); if (uri.empty()) { auto u = resourceParams.Get("uri"); uri = ExtractUri(u); } if (uri.empty()) { return promise.Reject("Invalid scene resource Image parameters given"); } if (auto resources = resources_.lock()) { if (const auto bitmap = resources->FetchBitmap(uri)) { // no aliasing.. so the returned bitmaps name is.. the old one. const auto result = FetchJsObj(bitmap).ToNapiValue(); return promise.Resolve(result); } } auto& obr = META_NS::GetObjectRegistry(); auto doc = interface_cast(obr.GetDefaultObjectContext()); auto renderContext = doc->GetProperty("RenderContext")->GetValue()->GetRenderer(); using LoadResult = CORE_NS::IImageLoaderManager::LoadResult; auto loadImage = [uri, renderContext]() { uint32_t imageLoaderFlags = CORE_NS::IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS; auto& imageLoaderMgr = renderContext->GetEngine().GetImageLoaderManager(); // LoadResult contains a unique pointer, so can't copy. Move it to the heap and pass a pointer instead. return BASE_NS::shared_ptr { new LoadResult { imageLoaderMgr.LoadImage(uri, imageLoaderFlags) } }; }; auto createGpuResource = [uri, renderContext]( BASE_NS::shared_ptr loadResult) -> SCENE_NS::IBitmap::Ptr { if (!loadResult->success) { LOG_E("%s", BASE_NS::string { "Failed to load image: " }.append(loadResult->error).c_str()); return {}; } auto& gpuResourceMgr = renderContext->GetDevice().GetGpuResourceManager(); GpuImageDesc gpuDesc = gpuResourceMgr.CreateGpuImageDesc(loadResult->image->GetImageDesc()); gpuDesc.usageFlags = CORE_IMAGE_USAGE_SAMPLED_BIT | CORE_IMAGE_USAGE_TRANSFER_DST_BIT; if (gpuDesc.engineCreationFlags & EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_GENERATE_MIPS) { gpuDesc.usageFlags |= CORE_IMAGE_USAGE_TRANSFER_SRC_BIT; } gpuDesc.memoryPropertyFlags = CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; const auto imageHandle = gpuResourceMgr.Create(uri, gpuDesc, std::move(loadResult->image)); auto& obr = META_NS::GetObjectRegistry(); auto doc = interface_pointer_cast(obr.GetDefaultObjectContext()); auto bitmap = META_NS::GetObjectRegistry().Create(SCENE_NS::ClassId::Bitmap, doc); if (auto m = interface_cast(bitmap)) { m->AddProperty(META_NS::ConstructProperty("Uri", uri)); } if (auto i = interface_cast(bitmap)) { i->SetRenderHandle(imageHandle); } return bitmap; }; auto convertToJs = [promise, uri, contextRef = NapiApi::StrongRef(ctx.This()), resources = GetResources(), paramRef = NapiApi::StrongRef(resourceParams)](SCENE_NS::IBitmap::Ptr bitmap) mutable { if (!bitmap) { promise.Reject(BASE_NS::string { "Failed to load image from URI " }.append(uri)); return; } const auto env = promise.Env(); napi_value args[] = { contextRef.GetValue(), paramRef.GetValue() }; const auto result = CreateFromNativeInstance(env, bitmap, PtrType::WEAK, args); auto renderContextJs = contextRef.GetObject().GetJsWrapper(); resources->StoreBitmap(uri, BASE_NS::move(bitmap)); promise.Resolve(result.ToNapiValue()); }; const auto ioQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(IO_QUEUE); const auto engineQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD); const auto jsQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP); META_NS::AddFutureTaskOrRunDirectly(ioQ, BASE_NS::move(loadImage)) .Then(BASE_NS::move(createGpuResource), engineQ) .Then(BASE_NS::move(convertToJs), jsQ); return promise; } napi_value RenderContextJS::CreateMeshResource(NapiApi::FunctionContext& ctx) { return ctx.GetUndefined(); } napi_value RenderContextJS::RegisterResourcePath(NapiApi::FunctionContext& ctx) { using namespace RENDER_NS; // 1.read current path of shader and protocol name auto registerProtocol = ctx.Arg<0>().valueOrDefault(); auto resourcePath = ctx.Arg<1>().valueOrDefault(); LOG_E("register resource path is: [%s], register protocol is : [%s]", resourcePath.c_str(), registerProtocol.c_str()); // 2.Check Empty for path & protocol if (resourcePath.empty() || registerProtocol.empty()) { LOG_E("Invalid register path or protocol of assets given"); return ctx.GetBoolean(false); } auto& obr = META_NS::GetObjectRegistry(); auto doc = interface_cast(obr.GetDefaultObjectContext()); auto& fileManager = doc->GetProperty("RenderContext") ->GetValue()->GetRenderer()->GetEngine().GetFileManager(); // 3.Check if the proxy protocol exists already. if (!(fileManager.CheckExistence(registerProtocol))) { LOG_E("Register protocol exists already"); return ctx.GetBoolean(false); } // 4.Check if the protocol is already declared. | Register now! if (!(fileManager.RegisterPath(registerProtocol.c_str(), resourcePath.c_str(), false))) { LOG_E("Register protocol declared already"); return ctx.GetBoolean(false); } return ctx.GetBoolean(true); }