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 "RenderContextJS.h"
17
18 #include <base/util/uid.h>
19
20 #include <meta/interface/intf_metadata.h>
21 #include <meta/interface/intf_object_context.h>
22
23 #include <scene/interface/intf_shader.h>
24 #include <scene/interface/intf_render_context.h>
25 #include <render/device/intf_gpu_resource_manager.h>
26
27
28 #include <core/image/intf_image_loader_manager.h>
29 #include <core/intf_engine.h>
30
31 #include <render/device/intf_gpu_resource_manager.h>
32
33 #include "JsObjectCache.h"
34 #include "ParamParsing.h"
35 #include "Promise.h"
36 #include "nodejstaskqueue.h"
37
38 static constexpr BASE_NS::Uid IO_QUEUE { "be88e9a0-9cd8-45ab-be48-937953dc258f" };
39
40 META_TYPE(BASE_NS::shared_ptr<CORE_NS::IImageLoaderManager::LoadResult>);
41
42 struct GlobalResources {
43 NapiApi::WeakRef defaultContext;
44 };
45
46 static BASE_NS::weak_ptr<GlobalResources> globalResources;
47
RenderResources(napi_env env)48 RenderResources::RenderResources(napi_env env) : env_(env)
49 {
50 // Acquire the js task queue. (make sure that as long as we have a scene, the nodejstaskqueue is useable)
51 if (auto jsQueue = interface_cast<INodeJSTaskQueue>(GetOrCreateNodeTaskQueue(env))) {
52 jsQueue->Acquire();
53 }
54 }
55
~RenderResources()56 RenderResources::~RenderResources()
57 {
58 disposeContainer_.DisposeAll(env_);
59
60 for (auto b : bitmaps_) {
61 b.second.reset();
62 }
63
64 // Release the js task queue.
65 auto tq = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP);
66 if (auto p = interface_cast<INodeJSTaskQueue>(tq)) {
67 p->Release();
68 // check if we can safely de-init here also.
69 if (p->IsReleased()) {
70 // destroy and unregister the queue.
71 DeinitNodeTaskQueue();
72 }
73 }
74 }
75
DisposeHook(uintptr_t token,NapiApi::Object obj)76 void RenderResources::DisposeHook(uintptr_t token, NapiApi::Object obj)
77 {
78 disposeContainer_.DisposeHook(token, obj);
79 }
80
ReleaseDispose(uintptr_t token)81 void RenderResources::ReleaseDispose(uintptr_t token)
82 {
83 disposeContainer_.ReleaseDispose(token);
84 }
85
StrongDisposeHook(uintptr_t token,NapiApi::Object obj)86 void RenderResources::StrongDisposeHook(uintptr_t token, NapiApi::Object obj)
87 {
88 disposeContainer_.StrongDisposeHook(token, obj);
89 }
90
ReleaseStrongDispose(uintptr_t token)91 void RenderResources::ReleaseStrongDispose(uintptr_t token)
92 {
93 disposeContainer_.ReleaseStrongDispose(token);
94 }
95
StoreBitmap(BASE_NS::string_view uri,SCENE_NS::IBitmap::Ptr bitmap)96 void RenderResources::StoreBitmap(BASE_NS::string_view uri, SCENE_NS::IBitmap::Ptr bitmap)
97 {
98 CORE_NS::UniqueLock lock(mutex_);
99 if (bitmap) {
100 bitmaps_[uri] = bitmap;
101 } else {
102 // setting null. releases.
103 bitmaps_.erase(uri);
104 }
105 }
106
FetchBitmap(BASE_NS::string_view uri)107 SCENE_NS::IBitmap::Ptr RenderResources::FetchBitmap(BASE_NS::string_view uri)
108 {
109 CORE_NS::UniqueLock lock(mutex_);
110 auto it = bitmaps_.find(uri);
111 if (it != bitmaps_.end()) {
112 return it->second;
113 }
114 return {};
115 }
116
Init(napi_env env,napi_value exports)117 void RenderContextJS::Init(napi_env env, napi_value exports)
118 {
119 using namespace NapiApi;
120 napi_property_descriptor props[] = {
121 // static methods
122 Method<NapiApi::FunctionContext<>, RenderContextJS, &RenderContextJS::GetResourceFactory>(
123 "getRenderResourceFactory"),
124 Method<NapiApi::FunctionContext<>, RenderContextJS, &RenderContextJS::Dispose>("destroy"),
125
126 // SceneResourceFactory methods
127 Method<NapiApi::FunctionContext<BASE_NS::string>, RenderContextJS, &RenderContextJS::LoadPlugin>("loadPlugin"),
128 Method<NapiApi::FunctionContext<NapiApi::Object>, RenderContextJS, &RenderContextJS::CreateShader>(
129 "createShader"),
130 Method<NapiApi::FunctionContext<NapiApi::Object>, RenderContextJS, &RenderContextJS::CreateImage>(
131 "createImage"),
132 Method<NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object>, RenderContextJS,
133 &RenderContextJS::CreateMeshResource>("createMesh"),
134 Method<NapiApi::FunctionContext<BASE_NS::string, BASE_NS::string>, RenderContextJS,
135 &RenderContextJS::RegisterResourcePath>("registerResourcePath"),
136 };
137
138 napi_value func;
139 auto status = napi_define_class(env, "RenderContext", NAPI_AUTO_LENGTH, BaseObject::ctor<RenderContextJS>(),
140 nullptr, sizeof(props) / sizeof(props[0]), props, &func);
141
142 napi_set_named_property(env, exports, "RenderContext", func);
143
144 NapiApi::MyInstanceState* mis;
145 NapiApi::MyInstanceState::GetInstance(env, reinterpret_cast<void**>(&mis));
146 if (mis) {
147 mis->StoreCtor("RenderContext", func);
148 }
149 }
150
RegisterEnums(NapiApi::Object exports)151 void RenderContextJS::RegisterEnums(NapiApi::Object exports) {}
152
RenderContextJS(napi_env env,napi_callback_info info)153 RenderContextJS::RenderContextJS(napi_env env, napi_callback_info info)
154 : BaseObject(env, info), env_(env), globalResources_(globalResources.lock())
155 {
156 LOG_V("RenderContextJS ++");
157 }
158
~RenderContextJS()159 RenderContextJS::~RenderContextJS()
160 {
161 LOG_V("RenderContextJS --");
162 }
163
GetInstanceImpl(uint32_t id)164 void* RenderContextJS::GetInstanceImpl(uint32_t id)
165 {
166 if (id == RenderContextJS::ID) {
167 return this;
168 }
169
170 return nullptr;
171 }
172
GetResources() const173 BASE_NS::shared_ptr<RenderResources> RenderContextJS::GetResources() const
174 {
175 if (auto resources = resources_.lock()) {
176 return resources;
177 }
178
179 BASE_NS::shared_ptr<RenderResources> resources(new RenderResources(env_));
180 resources_ = resources;
181
182 return resources;
183 }
184
GetDefaultContext(napi_env env)185 napi_value RenderContextJS::GetDefaultContext(napi_env env)
186 {
187 auto resources = globalResources.lock();
188 if (!resources) {
189 resources.reset(new GlobalResources);
190 globalResources = resources;
191 }
192
193 NapiApi::Object context = NapiApi::Object(env, "RenderContext", {});
194 resources->defaultContext = context;
195
196 return context.ToNapiValue();
197 }
198
Dispose(NapiApi::FunctionContext<> & ctx)199 napi_value RenderContextJS::Dispose(NapiApi::FunctionContext<>& ctx)
200 {
201 return {};
202 }
203
DisposeNative(void *)204 void RenderContextJS::DisposeNative(void*) {}
205
Finalize(napi_env env)206 void RenderContextJS::Finalize(napi_env env) {}
207
GetResourceFactory(NapiApi::FunctionContext<> & ctx)208 napi_value RenderContextJS::GetResourceFactory(NapiApi::FunctionContext<>& ctx)
209 {
210 return ctx.This().ToNapiValue();
211 }
212
LoadPlugin(NapiApi::FunctionContext<BASE_NS::string> & ctx)213 napi_value RenderContextJS::LoadPlugin(NapiApi::FunctionContext<BASE_NS::string>& ctx)
214 {
215 auto c = ctx.Arg<0>().valueOrDefault();
216 if (!BASE_NS::IsUidString(c)) {
217 LOG_E("\"%s\" is not a Uid string", c.c_str());
218
219 Promise promise(ctx.GetEnv());
220 NapiApi::Value<bool> result(ctx.GetEnv(), false);
221 promise.Resolve(result.ToNapiValue());
222
223 return promise.ToNapiValue();
224 }
225
226 LOG_E("Loading plugin: %s", c.c_str());
227
228 BASE_NS::Uid uid(*(char(*)[37])c.data());
229
230 const auto engineQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD);
231 META_NS::AddFutureTaskOrRunDirectly(engineQ, [uid]() {
232 Core::GetPluginRegister().LoadPlugins({uid});
233 });
234
235 Promise promise(ctx.GetEnv());
236 NapiApi::Value<bool> result(ctx.GetEnv(), true);
237 promise.Resolve(result.ToNapiValue());
238
239 return promise.ToNapiValue();
240 }
241
CreateShader(NapiApi::FunctionContext<NapiApi::Object> & ctx)242 napi_value RenderContextJS::CreateShader(NapiApi::FunctionContext<NapiApi::Object>& ctx)
243 {
244 return ctx.GetUndefined();
245 }
246
CreateImage(NapiApi::FunctionContext<NapiApi::Object> & ctx)247 napi_value RenderContextJS::CreateImage(NapiApi::FunctionContext<NapiApi::Object>& ctx)
248 {
249 // Create an image in four steps:
250 // 1. Parse args in JS thread (this function body)
251 // 2. Load image data in IO thread
252 // 3. Create GPU resource in engine thread
253 // 4. Settle promise by converting to JS object in JS release thread
254 using namespace RENDER_NS;
255 const auto env = ctx.GetEnv();
256 auto promise = Promise(env);
257
258 NapiApi::Object resourceParams = ctx.Arg<0>();
259 auto uri = ExtractUri(resourceParams.Get<NapiApi::Object>("uri"));
260 if (uri.empty()) {
261 auto u = resourceParams.Get<BASE_NS::string>("uri");
262 uri = ExtractUri(u);
263 }
264
265 if (uri.empty()) {
266 return promise.Reject("Invalid scene resource Image parameters given");
267 }
268
269 if (auto resources = resources_.lock()) {
270 if (const auto bitmap = resources->FetchBitmap(uri)) {
271 // no aliasing.. so the returned bitmaps name is.. the old one.
272 const auto result = FetchJsObj(bitmap).ToNapiValue();
273 return promise.Resolve(result);
274 }
275 }
276
277 auto& obr = META_NS::GetObjectRegistry();
278 auto doc = interface_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
279 auto renderContext = doc->GetProperty<SCENE_NS::IRenderContext::Ptr>("RenderContext")->GetValue()->GetRenderer();
280
281 using LoadResult = CORE_NS::IImageLoaderManager::LoadResult;
282 auto loadImage = [uri, renderContext]() {
283 uint32_t imageLoaderFlags = CORE_NS::IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS;
284 auto& imageLoaderMgr = renderContext->GetEngine().GetImageLoaderManager();
285 // LoadResult contains a unique pointer, so can't copy. Move it to the heap and pass a pointer instead.
286 return BASE_NS::shared_ptr<LoadResult> { new LoadResult { imageLoaderMgr.LoadImage(uri, imageLoaderFlags) } };
287 };
288
289 auto createGpuResource = [uri, renderContext](
290 BASE_NS::shared_ptr<LoadResult> loadResult) -> SCENE_NS::IBitmap::Ptr {
291 if (!loadResult->success) {
292 LOG_E("%s", BASE_NS::string { "Failed to load image: " }.append(loadResult->error).c_str());
293 return {};
294 }
295
296 auto& gpuResourceMgr = renderContext->GetDevice().GetGpuResourceManager();
297 GpuImageDesc gpuDesc = gpuResourceMgr.CreateGpuImageDesc(loadResult->image->GetImageDesc());
298 gpuDesc.usageFlags = CORE_IMAGE_USAGE_SAMPLED_BIT | CORE_IMAGE_USAGE_TRANSFER_DST_BIT;
299 if (gpuDesc.engineCreationFlags & EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_GENERATE_MIPS) {
300 gpuDesc.usageFlags |= CORE_IMAGE_USAGE_TRANSFER_SRC_BIT;
301 }
302 gpuDesc.memoryPropertyFlags = CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
303 const auto imageHandle = gpuResourceMgr.Create(uri, gpuDesc, std::move(loadResult->image));
304
305 auto& obr = META_NS::GetObjectRegistry();
306 auto doc = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
307 auto bitmap = META_NS::GetObjectRegistry().Create<SCENE_NS::IBitmap>(SCENE_NS::ClassId::Bitmap, doc);
308 if (auto m = interface_cast<META_NS::IMetadata>(bitmap)) {
309 m->AddProperty(META_NS::ConstructProperty<BASE_NS::string>("Uri", uri));
310 }
311 if (auto i = interface_cast<SCENE_NS::IRenderResource>(bitmap)) {
312 i->SetRenderHandle(imageHandle);
313 }
314 return bitmap;
315 };
316
317 auto convertToJs = [promise, uri, contextRef = NapiApi::StrongRef(ctx.This()), resources = GetResources(),
318 paramRef = NapiApi::StrongRef(resourceParams)](SCENE_NS::IBitmap::Ptr bitmap) mutable {
319 if (!bitmap) {
320 promise.Reject(BASE_NS::string { "Failed to load image from URI " }.append(uri));
321 return;
322 }
323 const auto env = promise.Env();
324 napi_value args[] = { contextRef.GetValue(), paramRef.GetValue() };
325 const auto result = CreateFromNativeInstance(env, bitmap, PtrType::WEAK, args);
326
327 auto renderContextJs = contextRef.GetObject().GetJsWrapper<RenderContextJS>();
328 resources->StoreBitmap(uri, BASE_NS::move(bitmap));
329
330 promise.Resolve(result.ToNapiValue());
331 };
332
333 const auto ioQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(IO_QUEUE);
334 const auto engineQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD);
335 const auto jsQ = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP);
336 META_NS::AddFutureTaskOrRunDirectly(ioQ, BASE_NS::move(loadImage))
337 .Then(BASE_NS::move(createGpuResource), engineQ)
338 .Then(BASE_NS::move(convertToJs), jsQ);
339
340 return promise;
341 }
342
CreateMeshResource(NapiApi::FunctionContext<NapiApi::Object,NapiApi::Object> & ctx)343 napi_value RenderContextJS::CreateMeshResource(NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object>& ctx)
344 {
345 return ctx.GetUndefined();
346 }
347
RegisterResourcePath(NapiApi::FunctionContext<BASE_NS::string,BASE_NS::string> & ctx)348 napi_value RenderContextJS::RegisterResourcePath(NapiApi::FunctionContext<BASE_NS::string, BASE_NS::string>& ctx)
349 {
350 using namespace RENDER_NS;
351
352 // 1.read current path of shader and protocol name
353 auto registerProtocol = ctx.Arg<0>().valueOrDefault();
354 auto resourcePath = ctx.Arg<1>().valueOrDefault();
355 LOG_E("register resource path is: [%s], register protocol is : [%s]",
356 resourcePath.c_str(), registerProtocol.c_str());
357
358 // 2.Check Empty for path & protocol
359 if (resourcePath.empty() || registerProtocol.empty()) {
360 LOG_E("Invalid register path or protocol of assets given");
361 return ctx.GetBoolean(false);
362 }
363
364 auto& obr = META_NS::GetObjectRegistry();
365 auto doc = interface_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
366 auto& fileManager = doc->GetProperty<SCENE_NS::IRenderContext::Ptr>("RenderContext")
367 ->GetValue()->GetRenderer()->GetEngine().GetFileManager();
368
369 // 3.Check if the proxy protocol exists already.
370 if (!(fileManager.CheckExistence(registerProtocol))) {
371 LOG_E("Register protocol exists already");
372 return ctx.GetBoolean(false);
373 }
374 // 4.Check if the protocol is already declared. | Register now!
375 if (!(fileManager.RegisterPath(registerProtocol.c_str(), resourcePath.c_str(), false))) {
376 LOG_E("Register protocol declared already");
377 return ctx.GetBoolean(false);
378 }
379
380 return ctx.GetBoolean(true);
381 }