Skip to content

Commit 713399b

Browse files
Copilotphilstopford
andcommitted
Implement clean sheet Veldrid backend switching architecture
Co-authored-by: philstopford <1983851+philstopford@users.noreply.github.com>
1 parent a1b7c6f commit 713399b

File tree

9 files changed

+696
-208
lines changed

9 files changed

+696
-208
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using Eto.Veldrid;
2+
3+
[assembly: Eto.ExportHandler(typeof(VeldridSurface), typeof(Eto.Veldrid.Gtk.GtkVeldridSurfaceHandler))]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Eto.Drawing;
2+
using Eto.Veldrid;
3+
using Gtk;
4+
using System;
5+
using Veldrid;
6+
7+
namespace Eto.Veldrid.Gtk.Backends;
8+
9+
/// <summary>
10+
/// Interface for handling different Veldrid graphics backends in GTK
11+
/// </summary>
12+
internal interface IVeldridBackendHandler : IDisposable
13+
{
14+
/// <summary>
15+
/// The graphics backend this handler supports
16+
/// </summary>
17+
GraphicsBackend Backend { get; }
18+
19+
/// <summary>
20+
/// Creates the appropriate GTK widget for this backend
21+
/// </summary>
22+
global::Gtk.Widget CreateWidget();
23+
24+
/// <summary>
25+
/// Creates a swapchain for this backend
26+
/// </summary>
27+
Swapchain? CreateSwapchain(VeldridSurface surface, Size renderSize);
28+
29+
/// <summary>
30+
/// Initializes the graphics device for this backend
31+
/// </summary>
32+
void InitializeGraphicsDevice(VeldridSurface surface, Size renderSize);
33+
34+
/// <summary>
35+
/// Handles resize events
36+
/// </summary>
37+
void HandleResize(Size newSize);
38+
39+
/// <summary>
40+
/// Forces a render/invalidation
41+
/// </summary>
42+
void Invalidate();
43+
44+
/// <summary>
45+
/// Sets up event handlers
46+
/// </summary>
47+
void SetupEventHandlers(VeldridSurface.ICallback callback, VeldridSurface surface);
48+
}
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using Veldrid;
3+
4+
namespace Eto.Veldrid.Gtk.Backends;
5+
6+
/// <summary>
7+
/// Factory for creating appropriate backend handlers
8+
/// </summary>
9+
internal static class VeldridBackendFactory
10+
{
11+
/// <summary>
12+
/// Creates a backend handler for the specified graphics backend
13+
/// </summary>
14+
public static IVeldridBackendHandler CreateBackendHandler(GraphicsBackend backend)
15+
{
16+
return backend switch
17+
{
18+
GraphicsBackend.OpenGL => new OpenGLBackendHandler(),
19+
GraphicsBackend.Vulkan => new VulkanBackendHandler(),
20+
_ => throw new NotSupportedException($"Graphics backend {backend} is not supported on GTK platform")
21+
};
22+
}
23+
24+
/// <summary>
25+
/// Checks if the specified backend is supported on the current system
26+
/// </summary>
27+
public static bool IsBackendSupported(GraphicsBackend backend)
28+
{
29+
switch (backend)
30+
{
31+
case GraphicsBackend.OpenGL:
32+
return GraphicsDevice.IsBackendSupported(GraphicsBackend.OpenGL);
33+
34+
case GraphicsBackend.Vulkan:
35+
return GraphicsDevice.IsBackendSupported(GraphicsBackend.Vulkan);
36+
37+
default:
38+
return false;
39+
}
40+
}
41+
42+
/// <summary>
43+
/// Gets the preferred backend for the current system
44+
/// </summary>
45+
public static GraphicsBackend GetPreferredBackend()
46+
{
47+
// Prefer Vulkan if available, otherwise fall back to OpenGL
48+
if (IsBackendSupported(GraphicsBackend.Vulkan))
49+
{
50+
return GraphicsBackend.Vulkan;
51+
}
52+
53+
if (IsBackendSupported(GraphicsBackend.OpenGL))
54+
{
55+
return GraphicsBackend.OpenGL;
56+
}
57+
58+
throw new NotSupportedException("No supported Veldrid backend found for GTK platform");
59+
}
60+
}

0 commit comments

Comments
 (0)