From d147343e93d82ef8af20755400bcb52091b94f73 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Thu, 9 Jul 2026 14:52:42 -0700 Subject: [PATCH 1/3] Use properly-awaiting playground revisions for GUI unicode viz tests Follow-up to BabylonJS/Babylon.js#18506, which fixed the two GUI unicode visualization playgrounds to return Promise from createScene (the old revisions fire-and-forget parseFromSnippetAsync / parseFromURLAsync, racing the screenshot), re-saved them as new revisions, and dropped the renderCount: 50 settle-frame workaround on the Babylon.js side. The Native viz runner already awaits the promise returned by createScene, so move to the awaiting revisions and drop the same workaround here: - Load GUI snippet with unicode: #YS93KY#0 -> #YS93KY#1 - Parse GUI json with unicode: #ERVGT5#0 -> #ERVGT5#1 - Remove renderCount: 50 from both (no longer needed now that setup is awaited) Same playground content, so the reference images are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Apps/Playground/Scripts/config.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index 1ef9f3c3f..1fedc6b5d 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -2274,14 +2274,12 @@ }, { "title": "Load GUI snippet with unicode", - "playgroundId": "#YS93KY#0", - "renderCount": 50, + "playgroundId": "#YS93KY#1", "referenceImage": "loadGuiSnippetWithUnicode.png" }, { "title": "Parse GUI json with unicode", - "playgroundId": "#ERVGT5#0", - "renderCount": 50, + "playgroundId": "#ERVGT5#1", "referenceImage": "parseGuiJsonWithUnicode.png" }, { From ff93f8303673911e724a35eba73069235148270d Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Thu, 9 Jul 2026 16:44:17 -0700 Subject: [PATCH 2/3] Keep renderCount settle frames for the Load GUI snippet test on Native CI showed the "Load GUI snippet with unicode" viz test fails without the settle frames: that GUI includes an async color-emoji image load that Babylon Native does not finish within a single frame (the button colors also haven't settled), so the emoji renders broken and the buttons render red. Babylon.js completes it fast enough, which is why #18506 could drop the settle frames there. Retain renderCount on that test; the "Parse GUI json with unicode" test has no image load and passes with the awaiting revision and no settle frames. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Apps/Playground/Scripts/config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index 1fedc6b5d..8e5fdf479 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -2275,6 +2275,7 @@ { "title": "Load GUI snippet with unicode", "playgroundId": "#YS93KY#1", + "renderCount": 50, "referenceImage": "loadGuiSnippetWithUnicode.png" }, { From 68fdb549795447f06eefdcaff39f3645763a2721 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Thu, 9 Jul 2026 21:11:11 -0700 Subject: [PATCH 3/3] Fix one-frame latency when a canvas texture feeds a same-frame consumer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NativeEngine::CopyTexture scheduled the canvas->texture blit on PeekNextViewId(). Once the scene/backbuffer view had already been acquired, that placed the copy on a view id AFTER the view that samples the destination. bgfx processes blits in numeric view-id order, so a fullscreen GUI AdvancedDynamicTexture layer sampled the previous frame's canvas content — a one-frame GUI latency (blank/stale first frame). The viz suite masked this by pinning the GUI unicode tests to large renderCount settle frames. Reserve the blit's view id in Context::Flush, immediately after the canvas draws but before the scene/backbuffer render is recorded, and hand it to the source texture so CopyTexture blits on a view ordered after the canvas Flush yet before the consuming view. Non-canvas copies keep the PeekNextViewId() fallback, preserving #1683's multi-canvas ordering fix. Drop the now-unnecessary renderCount from the two GUI unicode viz tests; with the fix they render correctly on the first frame. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Apps/Playground/Scripts/config.json | 1 - .../InternalInclude/Babylon/Graphics/Texture.h | 6 ++++++ Plugins/NativeEngine/Source/NativeEngine.cpp | 16 ++++++++++++---- Polyfills/Canvas/Source/Canvas.cpp | 3 +++ Polyfills/Canvas/Source/Canvas.h | 5 +++++ Polyfills/Canvas/Source/Context.cpp | 9 +++++++++ 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index 8e5fdf479..1fedc6b5d 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -2275,7 +2275,6 @@ { "title": "Load GUI snippet with unicode", "playgroundId": "#YS93KY#1", - "renderCount": 50, "referenceImage": "loadGuiSnippetWithUnicode.png" }, { diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h index b259a4fc7..8f4e2477c 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h @@ -44,6 +44,11 @@ namespace Babylon::Graphics uint16_t ViewNumLayers() const; void ViewNumLayers(uint16_t); + // View id reserved (by the Canvas polyfill) for the canvas->texture blit that fills + // this texture. UINT16_MAX means "unset"; consumers fall back to a freshly peeked view. + bgfx::ViewId BlitViewId() const { return m_blitViewId; } + void BlitViewId(bgfx::ViewId viewId) { m_blitViewId = viewId; } + private: bgfx::TextureHandle m_handle{bgfx::kInvalidHandle}; bool m_ownsHandle{false}; @@ -56,6 +61,7 @@ namespace Babylon::Graphics uint32_t m_samplerFlags{BGFX_SAMPLER_NONE}; uint16_t m_viewFirstLayer{0}; uint16_t m_viewNumLayers{0}; + bgfx::ViewId m_blitViewId{UINT16_MAX}; uintptr_t m_deviceID; DeviceContext& m_deviceContext; }; diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index 864031710..99c5e3484 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -1409,10 +1409,18 @@ namespace Babylon const auto textureSource = data.ReadPointer(); const auto textureDestination = data.ReadPointer(); - // Use a view id greater than every view used so far so the blit runs after - // all canvas Flushes that may have produced the source content (bgfx - // processes blits in numeric view-id order). See #1683. - const bgfx::ViewId blitView = m_deviceContext.PeekNextViewId(); + // The canvas->texture blit must run AFTER the canvas Flush that produced the source + // (bgfx processes blits in numeric view-id order) yet BEFORE the scene/backbuffer view + // that samples the destination, otherwise the consumer (e.g. a fullscreen GUI ADT layer) + // samples the previous frame's content — a one-frame latency. Context::Flush reserves a + // view id immediately after the canvas draws (before the scene render is recorded) and + // hands it to the source texture; use it here. Non-canvas sources have no reserved id, so + // fall back to PeekNextViewId() (a view greater than every view used so far). See #1683. + bgfx::ViewId blitView = textureSource->BlitViewId(); + if (blitView == UINT16_MAX) + { + blitView = m_deviceContext.PeekNextViewId(); + } encoder->blit(blitView, textureDestination->Handle(), 0, 0, textureSource->Handle()); } diff --git a/Polyfills/Canvas/Source/Canvas.cpp b/Polyfills/Canvas/Source/Canvas.cpp index a921c9c34..1bf61a81c 100644 --- a/Polyfills/Canvas/Source/Canvas.cpp +++ b/Polyfills/Canvas/Source/Canvas.cpp @@ -215,6 +215,9 @@ namespace Babylon::Polyfills::Internal } m_texture->Attach(bgfx::getTexture(m_frameBuffer->Handle()), false, m_width, m_height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); + // Hand the blit view id reserved during the preceding Context::Flush to the texture so + // NativeEngine::CopyTexture blits on a view ordered before the consuming layer. + m_texture->BlitViewId(m_blitViewId); return Napi::Pointer::Create(info.Env(), m_texture.get()); } diff --git a/Polyfills/Canvas/Source/Canvas.h b/Polyfills/Canvas/Source/Canvas.h index c475997b8..d93926e1f 100644 --- a/Polyfills/Canvas/Source/Canvas.h +++ b/Polyfills/Canvas/Source/Canvas.h @@ -71,6 +71,10 @@ namespace Babylon::Polyfills::Internal Babylon::Graphics::FrameBuffer& GetFrameBuffer() { return *m_frameBuffer; } FrameBufferPool m_frameBufferPool; + // View id reserved by Context::Flush (right after this canvas' draws) for the + // canvas->texture blit issued from NativeEngine::CopyTexture. See Context::Flush. + void SetBlitViewId(bgfx::ViewId viewId) { m_blitViewId = viewId; } + Graphics::DeviceContext& GetGraphicsContext() { return m_graphicsContext; @@ -99,6 +103,7 @@ namespace Babylon::Polyfills::Internal std::unique_ptr m_frameBuffer; std::unique_ptr m_texture{}; + bgfx::ViewId m_blitViewId{UINT16_MAX}; bool m_dirty{}; bool m_clear{}; diff --git a/Polyfills/Canvas/Source/Context.cpp b/Polyfills/Canvas/Source/Context.cpp index 0ce06df4f..74554765d 100644 --- a/Polyfills/Canvas/Source/Context.cpp +++ b/Polyfills/Canvas/Source/Context.cpp @@ -728,6 +728,15 @@ namespace Babylon::Polyfills::Internal nvgEndFrame(*m_nvg); frameBuffer.Unbind(); + // Reserve the view id for the eventual canvas->texture blit NOW, while we are + // sequenced immediately after this canvas' draws but before the scene/backbuffer + // render is recorded. bgfx processes blits in numeric view-id order, so the copy + // must land AFTER the canvas Flush (source ready) yet BEFORE the fullscreen ADT + // layer samples the destination texture. Deferring to CopyTexture's + // PeekNextViewId() would place the blit after the backbuffer view, so the layer + // would sample the previous frame's content (a one-frame GUI latency). + m_canvas->SetBlitViewId(m_graphicsContext.AcquireNewViewId()); + for (auto& buffer : m_canvas->m_frameBufferPool.GetPoolBuffers()) { // sanity check no unreleased buffers