Add synchronous CreateForJavaScript and deprecate AddToContextAsync in ExternalTexture#1646
Conversation
- Add Tests.ExternalTexture.Render.cpp: end-to-end test that renders a texture array through a ShaderMaterial to an external render target, verifying each slice (red, green, blue) via pixel readback. - Add tests.externalTexture.render.ts: JS test with sampler2DArray shader. - Add RenderDoc.h/cpp to UnitTests for optional GPU capture support. - Add Utils helpers: CreateTestTextureArrayWithData, CreateRenderTargetTexture, ReadBackRenderTarget, DestroyRenderTargetTexture (D3D11, Metal, stubs for D3D12/OpenGL). - Fix ExternalTexture_Shared.h: pass m_impl->NumLayers() instead of hardcoded 1 in Attach(), preserving texture array metadata. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove layerIndex parameter from Impl::Update declaration to match the updated signature in ExternalTexture_Shared.h. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrate HeadlessScreenshotApp, StyleTransferApp, and PrecompiledShaderTest from AddToContextAsync (promise-based) to CreateForJavaScript (synchronous). This removes the extra frame pump and promise callbacks that were previously required. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…llers - Move RenderDoc.h/cpp to D3D11-only CMake block with HAS_RENDERDOC define - Guard RenderDoc calls with HAS_RENDERDOC instead of WIN32 - Update StyleTransferApp and PrecompiledShaderTest to use CreateForJavaScript Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On OpenGL, TextureT is unsigned int (not a pointer), so reinterpret_cast fails. Add NativeHandleToUintPtr helper using if constexpr to handle both pointer types (D3D11/Metal/D3D12) and integer types (OpenGL). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- RenderDoc.h/cpp now accept void* device instead of ID3D11Device* - Move RenderDoc to WIN32 block (not D3D11-only) since it works with any API - Fix OpenGL build: use NativeHandleToUintPtr helper for TextureT cast - Add Linux support (dlopen librenderdoc.so) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DestroyTestTexture after FinishRenderingCurrentFrame so bgfx::frame() processes the texture creation command before the native resource is released. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8d48bcb to
bb4ec86
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensure the JS startup dispatch completes before calling deviceUpdate.Finish() and FinishRenderingCurrentFrame(). This guarantees that bgfx::frame() processes the CreateForJavaScript texture creation command, making the texture available for subsequent render frames. The old async API had an implicit sync point (addToContext.wait) that the new sync API lost. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eation - Rename CreateForJavaScriptWithTextureArray to CreateForJavaScript and use arraySize=1 since texture array rendering is covered by RenderTextureArray. The old test crashed on CI (STATUS_BREAKPOINT in bgfx when creating texture arrays via encoder on WARP). - Revert two-step create+override approach back to single createTexture2D call with _external parameter (overrideInternal from JS thread doesn't work since the D3D11 resource isn't created until bgfx::frame). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CreateForJavaScript already exists in Tests.ExternalTexture.cpp, causing a linker duplicate symbol error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… test) The D3D11-specific CreateForJavaScript test crashed on CI due to bgfx assertions when calling createTexture2D with _external on the encoder thread. The cross-platform CreateForJavaScript test in Tests.ExternalTexture.cpp already covers this functionality. The texture array rendering is covered by RenderTextureArray. Also revert app startup ordering to Finish->Wait (matching the pattern used by HeadlessScreenshotApp on master). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The bgfx callback's fatal() handler was silently calling debugBreak() on DebugCheck assertions with no output, making CI crashes impossible to diagnose. Now logs the file, line, error code and message to stderr before breaking, so the assertion details appear in CI logs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add DISM/d3dconfig step to CI to enable D3D debug layer, which will provide detailed D3D11 validation messages for the CreateShaderResourceView E_INVALIDARG failure. Kept the _external createTexture2D path (reverted the AfterRenderScheduler approach) so we can see the actual D3D debug output that explains the SRV mismatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The bgfx _external texture path triggers E_INVALIDARG in CreateShaderResourceView on CI's WARP D3D11 driver. The overrideInternal alternative doesn't support full array textures (hardcodes ArraySize=1). Since the _external path works on real GPUs, skip the render test on CI via BABYLON_NATIVE_SKIP_RENDER_TESTS and keep the direct _external path. Also adds D3D debug layer enablement to CI for future diagnostics, and logs bgfx fatal errors to stderr before crashing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch from _external parameter (crashes on WARP) to create+overrideInternal two-step approach. The overrideInternal path is compatible with WARP but sets ArraySize=1 in the SRV, so the RenderTextureArray test (which needs full array access) is skipped on CI. The render test works on real GPUs where the _external path succeeds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The overrideInternal call fires on AfterRenderScheduler after the first bgfx::frame(). An additional frame pump ensures the native texture backing is applied before the scene render. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove deleted Tests.ExternalTexture.D3D11.cpp from Install/Test/CMakeLists.txt - Add extra frame pump after CreateForJavaScript in HeadlessScreenshotApp and StyleTransferApp so overrideInternal has time to apply the native texture backing before the first render. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nder-test # Conflicts: # .github/jobs/win32.yml
…erResourceView The createTexture2D _external path now works on WARP after the bgfx update in master. Drop the placeholder + AfterRenderScheduler + overrideInternal two-step dance and the extra frame pump that existed to apply it. This also restores full array-slice SRV so RenderTextureArray no longer needs a sanitizer-only skip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove 'pump an extra frame' block from StyleTransferApp and HeadlessScreenshotApp (no longer needed after the _external revert). - Update ExternalTexture Readme to match the one-call createTexture2D path and direct-construction consumer pattern. - Drop unused DispatchSync declaration from UnitTests/Utils.h. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The SKIP_RENDER_TESTS flag was originally added because WARP could not handle the _external path in bgfx. bgfx PR BabylonJS#1669 fixed CreateShaderResourceView for the _external parameter, so the render test should pass on WARP now. Let CI verify. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Reword BABYLON_NATIVE_SKIP_RENDER_TESTS option description. WARP is available on Win32, so 'no real GPU' is inaccurate; the real reason to skip is a missing per-backend test harness. - Add braces around if constexpr / else branches in NativeHandleToUintPtr per coding style. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The mutex on ExternalTexture::Impl bridges graphics-thread state (m_info, m_ptr, m_textures) to JS-thread reads in CreateForJavaScript. Previously the outer ExternalTexture class reached through Impl::Mutex() to lock around bodies that mixed cached-field reads, bgfx calls, and napi object allocation. That shape is what produced BabylonJS#1646: a JS object allocation triggered a sibling Texture finalizer on the same thread, which re-entered the same mutex and aborted under MSVC's std::mutex. Move the lock inside Impl so the lock scope matches the actual cross-thread boundary, no more, no less: - ImplBase exposes two JS-thread entry points, CreateTexture (returns a fully-constructed Graphics::Texture*) and DestroyTexture (called from the napi finalizer). Each takes the mutex internally and contains no JS callouts, so the lock can never be held across user-visible JS execution. This structurally prevents the recursive-mutex bug at this call site -- not just the one already fixed in 415f193. - Impl::Update self-locks around the m_info / m_ptr / UpdateTextures publish, with explicit braces around the locked region. - The Mutex() accessor, AddTexture, and RemoveTexture are removed from ImplBase's public surface. m_mutex is no longer reachable from outside the impl. - ExternalTexture::Width/Height/Get/Update no longer lock. Per the public contract documented in ExternalTexture.h, every operation except CreateForJavaScript is graphics-thread-only and therefore serialized against Update() by single-threaded execution. The previous internal locks on these getters did not actually make them safe to call from other threads (no happens-before with prior Updates) and only obscured the real synchronization boundary. - ExternalTexture::CreateForJavaScript becomes a thin wrapper: resolve DeviceContext (JS lookup, must run unlocked), call Impl::CreateTexture, then Napi::Pointer::Create with a finalizer that delegates to Impl::DestroyTexture via weak_ptr. No platform-specific Impl files (D3D11/D3D12/Metal/OpenGL) need changes -- none of them touched the mutex or texture set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
[Responded by Copilot on behalf of @bghgary] Follow-up on the recursive-lock issue and audit, after the fix in 415f193. Refactor: encapsulate the
|
…abylonJS#1646 In BabylonJS#1674's combination of rework-thread-model (BabylonJS#1652) and the ExternalTexture sync API (BabylonJS#1646), three apps swapped the original async AddToContextAsync flow for synchronous CreateForJavaScript but kept the two-frame skeleton from the async version. The result is that the loader.Dispatch which runs the JS startup() callback now gets queued in frame 1 even though its observable wait (startup.get_future().wait()) is in frame 2 — the dispatch can land in either frame depending on JS-thread scheduling, breaking the "each Start/Finish pair wraps a phase of work" pattern. On rework-thread-model the JS thread will block on the closed gate between frames 1 and 2 anyway, so it functionally works, but the ordering is wrong. Move the dispatch into the second frame so the texture creation, startup() call, and the wait that observes them all run inside the same frame: frame 1: load scripts (no dispatch) frame 2: dispatch[startup] -> wait frame 3+: per-asset / render-loop Three apps had this issue; UnitTests' ExternalTexture tests already do it correctly. On bare rework-thread-model these apps still use AddToContextAsync and don't have the bug. Files: - Apps/HeadlessScreenshotApp/Win32/App.cpp - Apps/PrecompiledShaderTest/Source/App.cpp - Apps/StyleTransferApp/Win32/App.cpp Local build verification (Win32 RelWithDebInfo): - HeadlessScreenshotApp: builds cleanly - StyleTransferApp: builds cleanly - PrecompiledShaderTest: not configured in this Build/Win32; change is structurally identical to HeadlessScreenshotApp which compiled. [Created by Copilot on behalf of @bghgary] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Context Coverage for MSAA + ExternalTexture rendering. Both tests pass on master on D3D11 today; this PR characterizes what works so future device-loss / `updateWrappedNativeTexture` MSAA work has a regression baseline. ## Changes Two new gtest cases in `Tests.ExternalTexture.Msaa.cpp` that drive a JS-side `RenderTargetTexture` whose `colorAttachment` is a `wrapNativeTexture` over an MSAA GPU texture, then read back the pixels (resolving MSAA before staging): - `RenderWithMsaaSamples1` -- discriminator, asserts zero blend pixels along the rotated red-plane edges so the samples=4 case is diagnostic. - `RenderWithMsaaSamples4` -- asserts red/white blend pixels along the same edges. Cross-platform per the convention #1646 establishes: file lives in shared `SOURCES`, gated by `SKIP_EXTERNAL_TEXTURE_TESTS` / `SKIP_RENDER_TESTS`. CMake defines `SKIP_RENDER_TESTS` for D3D12 (no impl). Renames `Utils.{h,cpp,mm}` -> `Helpers.{h,cpp,mm}` under `namespace Helpers`. Collapses `CreateTestTexture` and the new MSAA-RT helper into one `Helpers::CreateTexture(d, w, h, arraySize=1, renderTarget=false, samples=1)`. Adds `Helpers::ReadPixels` taking `PlatformInfo` so the Metal path can use bgfx's command queue (avoids cross-queue sync). Implemented on D3D11 and Metal; D3D12 / OpenGL throw (test skipped on those configurations). Will conflict with #1646 on `Helpers.h` / `Helpers.D3D11.cpp` / `CMakeLists.txt` -- whichever lands first picks a winner. [Created by Copilot on behalf of @bghgary] --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MSAA tests deadlocked after merging master into rework-thread-model. The render thread waits on startupDone while the JS thread runs the AddToContextAsync .then() callback. That callback calls startup() -> new NativeEngine() / new Scene() / new RenderTargetTexture(), which eventually hits SubmitCommands. SubmitCommands now synchronously acquires a FrameCompletionScope, which blocks while m_frameBlocked is true. The gate only opens on the next StartRenderingCurrentFrame -- but the render thread is stuck on startupDone. Deadlock. Fix: open frame 2 before waiting on startupDone, and reuse the same frame for renderFrame(). Any JS work that touches the engine has to run while a frame is in progress under the new model. This workaround is temporary. Once #1646 lands, the test should migrate from AddToContextAsync to the new synchronous CreateForJavaScript, at which point startup() runs in the same JS task as the texture creation and the cross-frame dance disappears entirely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
With BabylonJS#1646's synchronous ExternalTexture::CreateForJavaScript and BabylonJS#1652's new threading model both in this branch, the original test pattern is both unnecessary and broken: - Unnecessary: AddToContextAsync's two-frame dance (queue in frame 1, wait for bgfx::frame() to resolve the promise, run startup in frame 2) exists only because the texture wrap was async. CreateForJavaScript is synchronous so the wrap, startup(), and renderFrame() all run in the same JS task inside a single frame. - Broken: under the new model, SubmitCommands synchronously acquires a FrameCompletionScope which blocks when no frame is in progress. The old between-frames startup() pattern deadlocks. Collapse to a single frame + single dispatch that wraps the texture, runs startup(), and chains renderFrame()'s promise resolution. The host app pattern host apps should follow with the new API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MSAA test was added on master using AddToContextAsync. This PR introduces synchronous CreateForJavaScript, so the test should use the new API directly rather than the deprecated shim. Collapse to a single frame + single dispatch that wraps the texture, runs startup(), and chains renderFrame()'s promise resolution -- the pattern host apps should follow with the new API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BabylonJS#1689 added the rendering-enabled check to DeviceImpl::UpdateDevice on master. Both BabylonJS#1646 and BabylonJS#1652 also have it. The check was dropped during this branch's merge with the older partner-test base, causing Device.UpdateDeviceThrowsWhenRenderingEnabled to fail in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Updates bgfx.cmake from e5f3f31c to 940054e1, which brings in Branimir's "setTexture view API" (bgfx #3728) plus the coordinated bgfx/bx update. The bgfx bump requires the same source churn upstream master already absorbed: - stb_image_resize.h was removed from bgfx's 3rdparty; switch to stb_image_resize2.h and the v2 entry point (stbir_resize_uint8_linear with STBIR_RGBA) in NativeEngine. - bx renamed uint32_cnttz/uint32_min to countTrailingZeros<>/min<> and moved them to bx/math.h; update nanovg_babylon accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an optional layerIndex to ExternalTexture::CreateForJavaScript and Update. When set, the wrapped JavaScript texture views only that one array layer of a multi-layer (e.g. NV12 decoder frame-pool) texture, selected at bind time via the bgfx setTexture view API. Graphics::Texture gains a view sub-range (ViewFirstLayer/ViewNumLayers); NativeEngine::SetTexture binds the single-slice view when ViewNumLayers != 0, otherwise the whole texture. The constructor is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a D3D11 regression test for the "green remote participants" bug: an NV12 Texture2DArray is wrapped as per-plane (Y=R8, UV=R8G8) single-slice ExternalTextures via layerIndex, and the selected slice's luma/chroma is passed through to the output unchanged. The test asserts the output exactly matches the target slice's YUV, catching the case where the per-plane UV view read the wrong array slice (or the Y plane) for slice >= 1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # CMakeLists.txt
The test drives device loss/restore via the async AddToContextAsync + manual frame-pump pattern, which deadlocks under the single-frame-encoder model in this PR. It is re-enabled in #1646 after migration to the synchronous CreateForJavaScript API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This reverts commit b7ad872.
Replaces the earlier skip. Under the single-frame-encoder model the async AddToContextAsync .then() runs startup()/restoreTexture() on the JS thread, whose SubmitCommands blocks until a frame is in progress. Open the next frame before each blocking wait on the async result (and reuse it for the following renderFrame), mirroring the workaround already applied to Tests.ExternalTexture.Msaa.cpp. Obsoleted once #1646 migrates these tests to the synchronous CreateForJavaScript API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
[Updated by Copilot on behalf of @bghgary] ## Context Replaces the old per-thread encoder model (`GetEncoderForThread`/`EndEncoders` + `UpdateToken` wrapping `SafeTimespanGuarantor`) with a **single frame encoder** owned by `DeviceImpl`, synchronized by a `FrameCompletionScope` RAII gate. `SafeTimespanGuarantor`, `UpdateToken`, and the `Update` classes are removed. `DeviceUpdate` remains as a no-op shim — removing it is an API break and a separate PR (#1653). ## Model `StartRenderingCurrentFrame` acquires the encoder (`bgfx::begin`) and opens the gate; `FinishRenderingCurrentFrame` waits for all `FrameCompletionScope`s to release, closes the gate, ends the encoder, and submits the frame. All consumers (NativeEngine, Canvas, NativeXr) read the shared encoder via `GetActiveEncoder()`. `SubmitCommands` and `ReadTexture` each hold a stack-scoped `FrameCompletionScope`, so the encoder can't be ended mid-work; when invoked outside a frame (e.g. an XHR callback) the scope blocks until the next frame opens. ``` Main Thread JS Thread bgfx render thread StartRenderingCurrentFrame() m_frameEncoder = bgfx::begin(true) m_frameBlocked = false; CV notify tick frameStartDispatcher -----> RAF callbacks submitCommands() AcquireFrameCompletionScope (immediate; frame open) GetEncoder() -> m_frameEncoder; draw... scope released; pendingScopes-- -> CV notify FinishRenderingCurrentFrame() wait scopes == 0; m_frameBlocked = true tick beforeRenderDispatcher bgfx::end(m_frameEncoder) Frame() -> bgfx::frame() ------------------------------> GPU submit & render tick afterRenderDispatcher ``` ## Synchronization rules 1. Single encoder, acquired before the gate opens (mutex gives memory ordering) and ended only after all scopes release. 2. `SubmitCommands`/`ReadTexture` always hold a scope — the encoder can't be ended mid-command, regardless of microtask draining or reentrancy. 3. Apps must bracket any main-thread wait for JS work with `Start`/`Finish`, or `SubmitCommands` blocks forever waiting for the gate (HeadlessScreenshotApp, StyleTransferApp, UnitTests do this). 4. `Canvas::Flush` acquires a scope when called outside a frame, then discards residual encoder state before nanovg rendering (the shared encoder carries NativeEngine state). ## Notes - `Tests.ExternalTexture.Msaa.cpp` opens the next frame before waiting on `startup()` — a workaround for the legacy async `AddToContextAsync` pattern, obsoleted once #1646's sync `CreateForJavaScript` lands. - `Tests.ExternalTexture.RestoreAfterDeviceLoss` (added on master after this branch) uses the same async pattern; the same frame-pump workaround is applied here. Obsoleted once #1646's sync `CreateForJavaScript` lands. - bgfx: added `m_encoderBeginLock` in `bgfx::frame()` to prevent a deadlock with `bgfx::destroy()`. - Follow-up: removing the `DeviceUpdate` no-op shim is an API-breaking change handled in #1653. --------- Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com> Co-authored-by: Gary Hsu <bghgary@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Conflict resolutions (final state matches the BabylonJS#1674 integration reference plus the layerIndex work from the Teams branch): - Apps (HeadlessScreenshot/PrecompiledShaderTest/StyleTransfer): combine BabylonJS#1652's frame-pump (open a startup frame so CreateForJavaScript's blit has an active frame) with BabylonJS#1646's synchronous CreateForJavaScript migration. - Tests.ExternalTexture.Msaa.cpp: BabylonJS#1646's synchronous CreateForJavaScript migration supersedes the frame-pump workaround BabylonJS#1652 added. - NativeEngine::SetTexture: combine BabylonJS#1652's GetEncoder() with BabylonJS#1646's layerIndex single-slice view overload. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The master merge brought in BabylonJS#1716's RestoreAfterDeviceLoss test using the async AddToContextAsync + frame-pump pattern (added by BabylonJS#1652). With BabylonJS#1646's synchronous CreateForJavaScript, that test runs startup() in the same JS task as the texture wrap, so the frame-pump is unnecessary; migrate it to match the other ExternalTexture tests. Also correct a now-stale comment in AddToContextAsyncAndUpdate: the deprecated AddToContextAsync shim resolves synchronously via CreateForJavaScript, so the frame finish no longer drives its completion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Conflict resolutions (final state matches the BabylonJS#1674 integration reference): - Apps (HeadlessScreenshot/PrecompiledShaderTest/StyleTransfer): combine BabylonJS#1652's frame-pump (open a startup frame so CreateForJavaScript's blit has an active frame) with BabylonJS#1646's synchronous CreateForJavaScript migration. - Tests.ExternalTexture.Msaa.cpp: BabylonJS#1646's synchronous CreateForJavaScript migration supersedes the frame-pump workaround BabylonJS#1652 added. - NativeEngine::SetTexture: combine BabylonJS#1652's GetEncoder() with BabylonJS#1646's layerIndex single-slice view overload. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The master merge brought in BabylonJS#1716's RestoreAfterDeviceLoss test using the async AddToContextAsync + frame-pump pattern (added by BabylonJS#1652). With BabylonJS#1646's synchronous CreateForJavaScript, that test runs startup() in the same JS task as the texture wrap, so the frame-pump is unnecessary; migrate it to match the other ExternalTexture tests. Also correct a now-stale comment in AddToContextAsyncAndUpdate: the deprecated AddToContextAsync shim resolves synchronously via CreateForJavaScript, so the frame finish no longer drives its completion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2cccc84 to
c75319f
Compare
[Updated by Copilot on behalf of @bghgary]
Changes
CreateForJavaScript(env, layerIndex = {})replaces the asyncAddToContextAsync(kept as a[[deprecated]]shim that wraps it), returning aNapi::Valuedirectly instead of aPromise. Existing ExternalTexture render/MSAA/DeviceLoss tests are migrated to the sync API.Recursive-mutex deadlock fix.
CreateForJavaScriptpreviously held the impl mutex across a JS property lookup that could run a siblingTexturefinalizer on the same thread, re-entering the samestd::mutexand aborting under MSVC.Graphics::DeviceContextis now resolved before the lock is taken, and the impl mutex is encapsulated so it is never held across a JS callout — making the bug shape structurally impossible.Single-slice layer selection.
CreateForJavaScript/Updatetake an optionallayerIndexto view one array layer of a multi-layer (e.g. NV12 decoder frame-pool) texture, selected at bind time via the bgfxsetTextureview API (#3728).Graphics::Texturecarries the view sub-range (ViewFirstLayer/ViewNumLayers) andNativeEngine::SetTexturebinds the single-slice view. Requires the bgfx.cmake bump to940054e1.