1+ using Eto . Drawing ;
2+ using Eto . Veldrid ;
3+ using Gtk ;
4+ using System ;
5+ using Veldrid ;
6+ using Veldrid . OpenGL ;
7+
8+ namespace Eto . Veldrid . Gtk . Backends ;
9+
10+ /// <summary>
11+ /// Backend handler for OpenGL using GTK's GLArea
12+ /// </summary>
13+ internal class OpenGLBackendHandler : IVeldridBackendHandler , VeldridSurface . IOpenGL
14+ {
15+ private GLArea ? _glArea ;
16+ private VeldridSurface . ICallback ? _callback ;
17+ private VeldridSurface ? _surface ;
18+ private bool _skipDraw ;
19+ private readonly System . Action _makeCurrent ;
20+ private readonly System . Action _clearCurrent ;
21+
22+ public GraphicsBackend Backend => GraphicsBackend . OpenGL ;
23+
24+ public OpenGLBackendHandler ( )
25+ {
26+ _makeCurrent = MakeCurrent ;
27+ _clearCurrent = ClearCurrent ;
28+ }
29+
30+ public global ::Gtk . Widget CreateWidget ( )
31+ {
32+ _glArea = new GLArea
33+ {
34+ CanFocus = true ,
35+ CanDefault = true ,
36+ HasDepthBuffer = true ,
37+ HasStencilBuffer = true
38+ } ;
39+
40+ // Veldrid technically supports as low as OpenGL 3.0, but the full
41+ // complement of features is only available with 3.3 and higher.
42+ _glArea . SetRequiredVersion ( 3 , 3 ) ;
43+
44+ return _glArea ;
45+ }
46+
47+ public Swapchain ? CreateSwapchain ( VeldridSurface surface , Size renderSize )
48+ {
49+ // For OpenGL, use the main swapchain from the graphics device
50+ return surface . GraphicsDevice ? . MainSwapchain ;
51+ }
52+
53+ public void InitializeGraphicsDevice ( VeldridSurface surface , Size renderSize )
54+ {
55+ if ( _glArea == null )
56+ return ;
57+
58+ // Make context current to manually initialize a Veldrid GraphicsDevice
59+ _glArea . Context . MakeCurrent ( ) ;
60+
61+ // Create the OpenGL graphics device
62+ surface . GraphicsDevice = GraphicsDevice . CreateOpenGL (
63+ surface . GraphicsDeviceOptions ,
64+ new OpenGLPlatformInfo (
65+ OpenGLContextHandle ,
66+ GetProcAddress ,
67+ MakeCurrent ,
68+ GetCurrentContext ,
69+ ClearCurrentContext ,
70+ DeleteContext ,
71+ SwapBuffers ,
72+ SetSyncToVerticalBlank ,
73+ SetSwapchainFramebuffer ,
74+ ResizeSwapchain ) ,
75+ ( uint ) renderSize . Width ,
76+ ( uint ) renderSize . Height ) ;
77+
78+ // Clear context in the worker thread for now to make Mesa happy
79+ if ( surface . GraphicsDevice ? . GetOpenGLInfo ( out BackendInfoOpenGL glInfo ) == true )
80+ {
81+ // This action has to wait so GTK can manage the context after this method
82+ glInfo . ExecuteOnGLThread ( _clearCurrent ) ;
83+ }
84+ }
85+
86+ public void HandleResize ( Size newSize )
87+ {
88+ _skipDraw = false ;
89+ _callback ? . OnResize ( _surface ! , new ResizeEventArgs ( newSize ) ) ;
90+ }
91+
92+ public void Invalidate ( )
93+ {
94+ _skipDraw = false ;
95+ _glArea ? . QueueRender ( ) ;
96+ }
97+
98+ public void SetupEventHandlers ( VeldridSurface . ICallback callback , VeldridSurface surface )
99+ {
100+ _callback = callback ;
101+ _surface = surface ;
102+
103+ if ( _glArea != null )
104+ {
105+ _glArea . Realized += OnGLAreaRealized ;
106+ _glArea . Render += OnGLAreaRender ;
107+ _glArea . Resize += OnGLAreaResize ;
108+ }
109+ }
110+
111+ private void OnGLAreaRealized ( object ? sender , EventArgs e )
112+ {
113+ if ( _glArea == null || _callback == null || _surface == null )
114+ return ;
115+
116+ var size = new Size ( ( int ) _glArea . AllocatedWidth , ( int ) _glArea . AllocatedHeight ) ;
117+ _callback . OnInitializeBackend ( _surface , new InitializeEventArgs ( size ) ) ;
118+ }
119+
120+ private void OnGLAreaRender ( object o , RenderArgs args )
121+ {
122+ if ( _skipDraw || _surface ? . GraphicsDevice == null )
123+ {
124+ _skipDraw = false ;
125+ return ;
126+ }
127+
128+ _skipDraw = true ;
129+
130+ // GTK makes the context current for us, so we need to clear it to hand it over to the Veldrid worker
131+ Gdk . GLContext . ClearCurrent ( ) ;
132+
133+ // Make context current on the Veldrid worker
134+ if ( _surface . GraphicsDevice . GetOpenGLInfo ( out BackendInfoOpenGL glInfo ) )
135+ {
136+ // No need for this action to wait, we just need it done some time before issuing commands
137+ glInfo . ExecuteOnGLThread ( _makeCurrent ) ;
138+ }
139+
140+ // It's important to only issue Veldrid commands in OnDraw,
141+ // since we only have a GL context current in the worker here
142+ _callback ? . OnDraw ( _surface , EventArgs . Empty ) ;
143+
144+ // Clear the context from the worker so GTK can use it again
145+ glInfo ? . ExecuteOnGLThread ( _clearCurrent ) ;
146+
147+ // GTK does not seem to need the context current after the Render event,
148+ // but setting it back is safer if this assumption changes in the future
149+ _glArea ? . MakeCurrent ( ) ;
150+
151+ _skipDraw = false ;
152+ }
153+
154+ private void OnGLAreaResize ( object o , ResizeArgs args )
155+ {
156+ var size = new Size ( ( int ) args . Width , ( int ) args . Height ) ;
157+ HandleResize ( size ) ;
158+ }
159+
160+ private void MakeCurrent ( )
161+ {
162+ _glArea ? . MakeCurrent ( ) ;
163+ }
164+
165+ private void ClearCurrent ( )
166+ {
167+ Gdk . GLContext . ClearCurrent ( ) ;
168+ }
169+
170+ // IOpenGL implementation
171+ public IntPtr OpenGLContextHandle => _glArea ? . Context . Handle ?? IntPtr . Zero ;
172+
173+ public IntPtr GetProcAddress ( string name ) => X11Interop . glXGetProcAddress ( name ) ;
174+
175+ public void MakeCurrent ( IntPtr context ) => MakeCurrent ( ) ;
176+
177+ public IntPtr GetCurrentContext ( ) => Gdk . GLContext . Current . Handle ;
178+
179+ public void ClearCurrentContext ( ) => ClearCurrent ( ) ;
180+
181+ public void DeleteContext ( IntPtr context )
182+ {
183+ // GTK manages the context lifecycle
184+ }
185+
186+ public void SwapBuffers ( )
187+ {
188+ // GLArea doesn't support drawing directly, so we queue a render but don't actually call OnDraw
189+ if ( _skipDraw )
190+ return ;
191+
192+ _skipDraw = true ;
193+ _glArea ? . QueueRender ( ) ;
194+ }
195+
196+ public void SetSyncToVerticalBlank ( bool enable )
197+ {
198+ // Handled by GTK/driver
199+ }
200+
201+ public void SetSwapchainFramebuffer ( )
202+ {
203+ // Not needed for GLArea
204+ }
205+
206+ public void ResizeSwapchain ( uint width , uint height )
207+ {
208+ // Handled automatically by GLArea
209+ }
210+
211+ public void Dispose ( )
212+ {
213+ if ( _glArea != null )
214+ {
215+ _glArea . Realized -= OnGLAreaRealized ;
216+ _glArea . Render -= OnGLAreaRender ;
217+ _glArea . Resize -= OnGLAreaResize ;
218+ _glArea = null ;
219+ }
220+ _callback = null ;
221+ _surface = null ;
222+ }
223+ }
0 commit comments