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" }, { 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