33#include " BgfxCallback.h"
44#include < bx/allocator.h>
55#include " continuation_scheduler.h"
6- #include " SafeTimespanGuarantor.h"
6+
7+ #include < arcana/threading/task.h>
78
89#include < napi/env.h>
910
1516
1617namespace Babylon ::Graphics
1718{
18- class Update ;
1919 class DeviceContext ;
2020 class DeviceImpl ;
2121
@@ -29,53 +29,38 @@ namespace Babylon::Graphics
2929 bgfx::TextureFormat::Enum Format{};
3030 };
3131
32- class UpdateToken final
32+ // FrameCompletionScope is an RAII guard that keeps a frame "open" on the JS thread.
33+ // While any scope is alive, FinishRenderingCurrentFrame() on the main thread will
34+ // block — it cannot call bgfx::frame() until all scopes are destroyed.
35+ //
36+ // This prevents a race where the main thread submits a bgfx frame while the JS
37+ // thread is still recording encoder commands (which would cause bgfx deadlocks
38+ // or lost draw calls).
39+ //
40+ // Three usage patterns:
41+ // 1. RAF scheduling: scope acquired on main thread during StartRenderingCurrentFrame,
42+ // transferred to JS thread, released after RAF callbacks complete + one extra
43+ // dispatch cycle (to cover GC-triggered resource destruction).
44+ // 2. NativeEngine::GetEncoder(): scope acquired lazily when JS code uses the encoder
45+ // outside RAF (e.g., async texture loads, LOD switches). Released on next dispatch.
46+ // 3. Canvas::Flush(): stack-scoped for the duration of nanovg rendering.
47+ //
48+ // Construction blocks if m_frameBlocked is true (frame submission in progress).
49+ // Destruction decrements counter and wakes main thread via condition variable.
50+ class FrameCompletionScope final
3351 {
3452 public:
35- UpdateToken (const UpdateToken& other) = delete ;
36- UpdateToken& operator =(const UpdateToken& other) = delete ;
37-
38- UpdateToken (UpdateToken&&) noexcept = default ;
39-
40- // The move assignment of `SafeTimespanGuarantor::SafetyGuarantee` is marked as delete.
41- // See https://github.com/Microsoft/GSL/issues/705.
42- // UpdateToken& operator=(UpdateToken&& other) = delete;
53+ FrameCompletionScope (const FrameCompletionScope&) = delete ;
54+ FrameCompletionScope& operator =(const FrameCompletionScope&) = delete ;
55+ FrameCompletionScope& operator =(FrameCompletionScope&&) = delete ;
4356
44- bgfx::Encoder* GetEncoder ();
45-
46- private:
47- friend class Update ;
48-
49- UpdateToken (DeviceContext&, SafeTimespanGuarantor&);
50-
51- DeviceContext& m_context;
52- SafeTimespanGuarantor::SafetyGuarantee m_guarantee;
53- };
54-
55- class Update
56- {
57- public:
58- continuation_scheduler<>& Scheduler ()
59- {
60- return m_safeTimespanGuarantor.OpenScheduler ();
61- }
62-
63- UpdateToken GetUpdateToken ()
64- {
65- return {m_context, m_safeTimespanGuarantor};
66- }
57+ FrameCompletionScope (FrameCompletionScope&&) noexcept ;
58+ ~FrameCompletionScope ();
6759
6860 private:
6961 friend class DeviceContext ;
70-
71- Update (SafeTimespanGuarantor& safeTimespanGuarantor, DeviceContext& context)
72- : m_safeTimespanGuarantor{safeTimespanGuarantor}
73- , m_context{context}
74- {
75- }
76-
77- SafeTimespanGuarantor& m_safeTimespanGuarantor;
78- DeviceContext& m_context;
62+ FrameCompletionScope (DeviceImpl&);
63+ DeviceImpl* m_impl;
7964 };
8065
8166 class DeviceContext
@@ -93,7 +78,19 @@ namespace Babylon::Graphics
9378 continuation_scheduler<>& BeforeRenderScheduler ();
9479 continuation_scheduler<>& AfterRenderScheduler ();
9580
96- Update GetUpdate (const char * updateName);
81+ // Scheduler that fires when StartRenderingCurrentFrame ticks the frame start dispatcher.
82+ // Use this to schedule work (e.g., requestAnimationFrame callbacks) that should run each frame.
83+ continuation_scheduler<>& FrameStartScheduler ();
84+
85+ // Acquire a scope that prevents FinishRenderingCurrentFrame from completing.
86+ // The scope must be held while JS frame callbacks are running.
87+ FrameCompletionScope AcquireFrameCompletionScope ();
88+
89+ // Active encoder for the current frame. Managed by DeviceImpl in
90+ // StartRenderingCurrentFrame/FinishRenderingCurrentFrame.
91+ // Used by NativeEngine, Canvas, and NativeXr.
92+ void SetActiveEncoder (bgfx::Encoder* encoder);
93+ bgfx::Encoder* GetActiveEncoder ();
9794
9895 void RequestScreenShot (std::function<void (std::vector<uint8_t >)> callback);
9996 void SetRenderResetCallback (std::function<void ()> callback);
@@ -113,7 +110,7 @@ namespace Babylon::Graphics
113110 using CaptureCallbackTicketT = arcana::ticketed_collection<std::function<void (const BgfxCallback::CaptureData&)>>::ticket;
114111 CaptureCallbackTicketT AddCaptureCallback (std::function<void (const BgfxCallback::CaptureData&)> callback);
115112
116- bgfx::ViewId AcquireNewViewId (bgfx::Encoder& );
113+ bgfx::ViewId AcquireNewViewId ();
117114
118115 // TODO: find a different way to get the texture info for frame capture
119116 void AddTexture (bgfx::TextureHandle handle, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format);
@@ -122,8 +119,6 @@ namespace Babylon::Graphics
122119 static bx::AllocatorI& GetDefaultAllocator () { return m_allocator; }
123120
124121 private:
125- friend UpdateToken;
126-
127122 DeviceImpl& m_graphicsImpl;
128123
129124 std::unordered_map<uint16_t , TextureInfo> m_textureHandleToInfo{};
0 commit comments