@@ -90,8 +90,6 @@ TEST(ExternalTexture, RestoreAfterDeviceLoss)
9090 device.GetPlatformInfo ().Device , TEX_SIZE , TEX_SIZE , 1 , true );
9191 Babylon::Plugins::ExternalTexture externalTexture{nativeTexture1};
9292
93- std::promise<void > startupDone;
94-
9593 Babylon::AppRuntime::Options options{};
9694 options.UnhandledExceptionHandler = [](const Napi::Error& error) {
9795 std::cerr << " [Uncaught Error] " << Napi::GetErrorString (error) << std::endl;
@@ -114,43 +112,33 @@ TEST(ExternalTexture, RestoreAfterDeviceLoss)
114112 loader.LoadScript (" app:///Assets/babylon.max.js" );
115113 loader.LoadScript (" app:///Assets/tests.externalTexture.deviceLoss.js" );
116114
117- // Queue AddToContextAsync and call startup() with the wrapped texture.
118- std::promise<void > addToContextCalled;
119- loader.Dispatch ([&externalTexture, &addToContextCalled, &startupDone](Napi::Env env) {
120- auto jsPromise = externalTexture.AddToContextAsync (env);
121- addToContextCalled.set_value ();
122-
123- auto jsOnFulfilled = Napi::Function::New (env, [&startupDone](const Napi::CallbackInfo& info) {
124- auto jsNativeTexture = info[0 ];
125- info.Env ().Global ().Get (" startup" ).As <Napi::Function>().Call ({
115+ // CreateForJavaScript is synchronous so startup() runs in the same JS task as the texture wrap.
116+ std::promise<void > startupDone;
117+ loader.Dispatch ([&externalTexture, &startupDone](Napi::Env env) {
118+ try
119+ {
120+ auto jsNativeTexture = externalTexture.CreateForJavaScript (env);
121+ env.Global ().Get (" startup" ).As <Napi::Function>().Call ({
126122 jsNativeTexture,
127- Napi::Number::New (info. Env () , TEX_SIZE ),
128- Napi::Number::New (info. Env () , TEX_SIZE ),
123+ Napi::Number::New (env , TEX_SIZE ),
124+ Napi::Number::New (env , TEX_SIZE ),
129125 });
130126 startupDone.set_value ();
131- });
132-
133- auto jsOnRejected = Napi::Function::New (env, [&startupDone](const Napi::CallbackInfo& info) {
134- startupDone.set_exception (std::make_exception_ptr (
135- std::runtime_error{Napi::GetErrorString (info[0 ].As <Napi::Error>())}));
136- });
137-
138- jsPromise.Get (" then" ).As <Napi::Function>().Call (jsPromise, {jsOnFulfilled, jsOnRejected});
127+ }
128+ catch (...)
129+ {
130+ startupDone.set_exception (std::current_exception ());
131+ }
139132 });
140133
141- addToContextCalled .get_future ().wait ();
134+ startupDone .get_future ().get ();
142135 update.Finish ();
143136 device.FinishRenderingCurrentFrame ();
144137
145- // Open the next frame BEFORE waiting on startup(): under the reworked single-frame-encoder
146- // model the AddToContextAsync .then() runs startup() on the JS thread, whose SubmitCommands
147- // blocks until a frame is in progress. The same frame is reused for Phase 1's renderFrame.
138+ // --- Phase 1: render red into texture 1, readback ---
148139 device.StartRenderingCurrentFrame ();
149140 update.Start ();
150141
151- startupDone.get_future ().get ();
152-
153- // --- Phase 1: render red into texture 1, readback (reuses the open frame) ---
154142 std::promise<void > render1Done;
155143 loader.Dispatch ([&render1Done](Napi::Env env) {
156144 auto jsPromise = env.Global ().Get (" renderFrame" ).As <Napi::Function>().Call ({}).As <Napi::Promise>();
@@ -203,37 +191,27 @@ TEST(ExternalTexture, RestoreAfterDeviceLoss)
203191 update.Start ();
204192
205193 std::promise<void > restoreDone;
206- std::promise<void > addToContext2Called;
207- loader.Dispatch ([&externalTexture2, &addToContext2Called, &restoreDone](Napi::Env env) {
208- auto jsPromise = externalTexture2.AddToContextAsync (env);
209- addToContext2Called.set_value ();
210-
211- auto jsOnFulfilled = Napi::Function::New (env, [&restoreDone](const Napi::CallbackInfo& info) {
212- auto jsNewNativeTexture = info[0 ];
213- info.Env ().Global ().Get (" restoreTexture" ).As <Napi::Function>().Call ({jsNewNativeTexture});
194+ loader.Dispatch ([&externalTexture2, &restoreDone](Napi::Env env) {
195+ try
196+ {
197+ auto jsNewNativeTexture = externalTexture2.CreateForJavaScript (env);
198+ env.Global ().Get (" restoreTexture" ).As <Napi::Function>().Call ({jsNewNativeTexture});
214199 restoreDone.set_value ();
215- });
216-
217- auto jsOnRejected = Napi::Function::New (env, [&restoreDone](const Napi::CallbackInfo& info) {
218- restoreDone.set_exception (std::make_exception_ptr (
219- std::runtime_error{Napi::GetErrorString (info[0 ].As <Napi::Error>())}));
220- });
221-
222- jsPromise.Get (" then" ).As <Napi::Function>().Call (jsPromise, {jsOnFulfilled, jsOnRejected});
200+ }
201+ catch (...)
202+ {
203+ restoreDone.set_exception (std::current_exception ());
204+ }
223205 });
224206
225- addToContext2Called .get_future ().wait ();
207+ restoreDone .get_future ().get ();
226208 update.Finish ();
227209 device.FinishRenderingCurrentFrame ();
228210
229- // Open the next frame before waiting on restoreTexture(): SubmitCommands needs an open frame
230- // under the reworked model. The same frame is reused for the blue render below.
211+ // Render blue into restored RTT.
231212 device.StartRenderingCurrentFrame ();
232213 update.Start ();
233214
234- restoreDone.get_future ().get ();
235-
236- // Render blue into restored RTT (reuses the open frame).
237215 std::promise<void > render2Done;
238216 loader.Dispatch ([&render2Done](Napi::Env env) {
239217 auto jsPromise = env.Global ().Get (" renderFrame" ).As <Napi::Function>().Call ({}).As <Napi::Promise>();
0 commit comments