diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
index fbe6a77974..887e375877 100644
--- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
+++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
@@ -10,6 +10,10 @@
+
+
+
+
diff --git a/src/Avalonia.OpenGL/Egl/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs
index b4fe8a99eb..295acef44b 100644
--- a/src/Avalonia.OpenGL/Egl/EglConsts.cs
+++ b/src/Avalonia.OpenGL/Egl/EglConsts.cs
@@ -35,7 +35,7 @@ namespace Avalonia.OpenGL.Egl
// public const int EGL_MAX_PBUFFER_PIXELS = 0x302B;
// public const int EGL_MAX_PBUFFER_WIDTH = 0x302C;
// public const int EGL_NATIVE_RENDERABLE = 0x302D;
-// public const int EGL_NATIVE_VISUAL_ID = 0x302E;
+ public const int EGL_NATIVE_VISUAL_ID = 0x302E;
// public const int EGL_NATIVE_VISUAL_TYPE = 0x302F;
public const int EGL_NONE = 0x3038;
// public const int EGL_NON_CONFORMANT_CONFIG = 0x3051;
@@ -200,6 +200,9 @@ namespace Avalonia.OpenGL.Egl
// EXT_platform_device
public const int EGL_PLATFORM_DEVICE_EXT = 0x313F;
+ // KHR_platform_x11 / EXT_platform_x11
+ public const int EGL_PLATFORM_X11_EXT = 0x31D5;
+
//EXT_device_query
public const int EGL_DEVICE_EXT = 0x322C;
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs
index 373aa0e964..1aefc20850 100644
--- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs
+++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs
@@ -45,7 +45,7 @@ namespace Avalonia.OpenGL.Egl
if(_display == IntPtr.Zero)
throw new ArgumentException();
- _config = EglDisplayUtils.InitializeAndGetConfig(_egl, display, options.GlVersions);
+ _config = EglDisplayUtils.InitializeAndGetConfig(_egl, display, options.GlVersions, options.ProbeConfig);
}
public EglInterface EglInterface => _egl;
@@ -84,7 +84,18 @@ namespace Avalonia.OpenGL.Egl
}
}
- var ctx = _egl.CreateContext(_display, Config, share?.Context ?? IntPtr.Zero, _config.Attributes);
+ var previousApi = _egl.QueryApi();
+ _egl.BindApi(_config.Api);
+ IntPtr ctx;
+ try
+ {
+ ctx = _egl.CreateContext(_display, Config, share?.Context ?? IntPtr.Zero, _config.Attributes);
+ }
+ finally
+ {
+ if (previousApi != EGL_NONE)
+ _egl.BindApi(previousApi);
+ }
if (ctx == IntPtr.Zero)
{
var ex = OpenGlException.GetFormattedException("eglCreateContext", _egl);
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
index a2cbeb5b3d..2963890d32 100644
--- a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
+++ b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
@@ -3,6 +3,14 @@ using System.Collections.Generic;
namespace Avalonia.OpenGL.Egl;
+///
+/// Given every EGL config matched by eglChooseConfig, returns the one that should be used, or null if none
+/// are usable. This lets platforms filter out broken configs that can't be distinguished by their attributes
+/// (e.g. nvidia exposes duplicate, partially broken configs) and impose their own preference order between
+/// usable ones (e.g. preferring a transparent/32-bit X11 visual, which mesa lists after the opaque ones).
+///
+public delegate IntPtr? EglConfigProbeCallback(EglInterface egl, IntPtr display, IntPtr[] configs);
+
public class EglDisplayOptions
{
public EglInterface? Egl { get; set; }
@@ -12,6 +20,7 @@ public class EglDisplayOptions
public Func? DeviceLostCheckCallback { get; set; }
public Action? DisposeCallback { get; set; }
public IEnumerable? GlVersions { get; set; }
+ public EglConfigProbeCallback? ProbeConfig { get; set; }
}
public class EglContextOptions
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
index 6ca7ab70c2..b214f6d85e 100644
--- a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
+++ b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
@@ -29,7 +29,28 @@ internal static class EglDisplayUtils
return display;
}
- public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable? versions)
+ // Enumerates every config matching the attribute list and lets the probe callback pick one (or simply
+ // takes the first one if no probe is supplied).
+ private static IntPtr? ChooseConfigWithProbe(EglInterface egl, IntPtr display, int[] attribs,
+ EglConfigProbeCallback? probe)
+ {
+ if (!egl.ChooseConfigs(display, attribs, null, 0, out var numConfigs) || numConfigs == 0)
+ return null;
+
+ var configs = new IntPtr[numConfigs];
+ if (!egl.ChooseConfigs(display, attribs, configs, configs.Length, out numConfigs) || numConfigs == 0)
+ return null;
+ if (numConfigs != configs.Length)
+ Array.Resize(ref configs, numConfigs);
+
+ if (probe == null)
+ return configs[0];
+
+ return probe(egl, display, configs);
+ }
+
+ public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display,
+ IEnumerable? versions, EglConfigProbeCallback? probeConfig = null)
{
if (!egl.Initialize(display, out _, out _))
throw OpenGlException.GetFormattedException("eglInitialize", egl);
@@ -122,11 +143,8 @@ internal static class EglDisplayUtils
EGL_DEPTH_SIZE, depthSize,
EGL_NONE
};
- if (!egl.ChooseConfig(display, attribs, out var config, 1, out int numConfigs))
+ if (ChooseConfigWithProbe(egl, display, attribs, probeConfig) is not { } config)
continue;
- if (numConfigs == 0)
- continue;
-
egl.GetConfigAttrib(display, config, EGL_SAMPLES, out var sampleCount);
egl.GetConfigAttrib(display, config, EGL_STENCIL_SIZE, out var returnedStencilSize);
diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
index 8ee06266d8..d3deb3d102 100644
--- a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
+++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
@@ -53,6 +53,21 @@ namespace Avalonia.OpenGL.Egl
Context.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0);
+ // Workaround for driver quirk https://github.com/NVIDIA/egl-wayland2/issues/46
+ // This is NVIDIA-specific, but setting buffers to GL_BACK won't hurt for other drivers too
+ if (Context.Version.Type == GlProfileType.OpenGL)
+ {
+ var gl = Context.GlInterface;
+ gl.Viewport(0, 0, size.Width, size.Height);
+ if (gl.IsReadBufferAvailable)
+ gl.ReadBuffer(GlConsts.GL_BACK);
+ if (gl.IsWriteBufferAvailable)
+ gl.WriteBuffer(GlConsts.GL_BACK);
+ if(gl.IsDrawBufferAvailable)
+ gl.DrawBuffer(GlConsts.GL_BACK);
+ }
+
+
success = true;
return new Session(Context.Display, Context, surface, size, scaling, restoreContext, onFinish, isYFlipped, SkipWaits);
}
diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs
index a0210b544e..6b83731b49 100644
--- a/src/Avalonia.OpenGL/Egl/EglInterface.cs
+++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs
@@ -75,7 +75,14 @@ namespace Avalonia.OpenGL.Egl
[GetProcAddress("eglChooseConfig")]
public partial bool ChooseConfig(IntPtr display, int[] attribs,
out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
-
+
+ // Returns all configs matching the attribute list. Pass a null `configs` array to query the
+ // available config count first. Some drivers (notably nvidia) expose multiple indistinguishable
+ // configs where only a subset is actually usable, so callers need to enumerate and probe them.
+ [GetProcAddress("eglChooseConfig")]
+ public partial bool ChooseConfigs(IntPtr display, int[] attribs,
+ IntPtr[]? configs, int configSize, out int numConfigs);
+
[GetProcAddress("eglCreateContext")]
public partial IntPtr CreateContext(IntPtr display, IntPtr config,
IntPtr share, int[] attrs);
diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
index cf81999095..df0f80f1ce 100644
--- a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
+++ b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
@@ -7,6 +7,7 @@ namespace Avalonia.OpenGL.Egl
public sealed class EglPlatformGraphics : IPlatformGraphics
{
private readonly EglDisplay _display;
+ internal EglDisplay Display => _display;
public bool UsesSharedContext => false;
public IPlatformGraphicsContext CreateContext() => _display.CreateContext(null);
public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
diff --git a/src/Avalonia.OpenGL/GlConsts.cs b/src/Avalonia.OpenGL/GlConsts.cs
index c1e1df0639..98b6e42da3 100644
--- a/src/Avalonia.OpenGL/GlConsts.cs
+++ b/src/Avalonia.OpenGL/GlConsts.cs
@@ -85,7 +85,7 @@ namespace Avalonia.OpenGL
// public const int GL_CW = 0x0900;
// public const int GL_CCW = 0x0901;
// public const int GL_FRONT = 0x0404;
-// public const int GL_BACK = 0x0405;
+ public const int GL_BACK = 0x0405;
// public const int GL_POLYGON_MODE = 0x0B40;
// public const int GL_POLYGON_SMOOTH = 0x0B41;
// public const int GL_POLYGON_STIPPLE = 0x0B42;
diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs
index 63d265c41e..55eab4563b 100644
--- a/src/Avalonia.OpenGL/GlInterface.cs
+++ b/src/Avalonia.OpenGL/GlInterface.cs
@@ -388,6 +388,15 @@ namespace Avalonia.OpenGL
[GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")]
public partial void GenVertexArrays(int n, int* rv);
+ [GetProcAddress("glReadBuffer", true)]
+ public partial void ReadBuffer(int buffer);
+
+ [GetProcAddress("glDrawBuffer", true)]
+ public partial void DrawBuffer(int buffer);
+
+ [GetProcAddress("glWriteBuffer", true)]
+ public partial void WriteBuffer(int buffer);
+
public int GenVertexArray()
{
int rv = 0;
diff --git a/src/Avalonia.X11/X11EglHelper.cs b/src/Avalonia.X11/X11EglHelper.cs
new file mode 100644
index 0000000000..2cb70a998a
--- /dev/null
+++ b/src/Avalonia.X11/X11EglHelper.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Logging;
+using Avalonia.OpenGL.Egl;
+using static Avalonia.OpenGL.Egl.EglConsts;
+using static Avalonia.X11.XLib;
+
+namespace Avalonia.X11
+{
+ internal static class X11EglHelper
+ {
+ ///
+ /// Resolves the that matches the EGL config's native visual id.
+ /// nvidia's driver requires the X11 window visual to match the config used to create the surface,
+ /// otherwise eglCreateWindowSurface fails. Returns null when the config doesn't advertise a visual id
+ /// (e.g. mesa, where any visual works).
+ ///
+ public static XVisualInfo? GetVisualInfo(X11Info x11, EglInterface egl, IntPtr display, IntPtr config)
+ {
+ if (!egl.GetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, out var visualId) || visualId == 0)
+ return null;
+ return XGetVisualInfoById(x11.DeferredDisplay, new IntPtr(visualId));
+ }
+
+ public static XVisualInfo? GetVisualInfo(X11Info x11, EglDisplay display) =>
+ GetVisualInfo(x11, display.EglInterface, display.Handle, display.Config);
+
+ ///
+ /// Picks the EGL config to use out of the ones matched by eglChooseConfig. Each candidate is verified by
+ /// creating a throwaway window with the config's native visual and attempting to create an EGL window
+ /// surface on it: nvidia exposes multiple identical-looking configs where only a subset actually works,
+ /// so the broken ones are discarded here. A 32-bit (transparent-capable) X11 visual is preferred,
+ /// mirroring the GLX backend which selects a 32-bit visual; mesa lists those configs after the opaque
+ /// ones. We resolve the (cheap) native visuals up-front and probe in preference order, so the expensive
+ /// window-surface creation is attempted as few times as possible.
+ ///
+ public static IntPtr? ChooseConfig(X11Info x11, EglInterface egl, IntPtr display, IntPtr[] configs)
+ {
+ var candidates = new List<(IntPtr config, XVisualInfo visual)>(configs.Length);
+ foreach (var config in configs)
+ {
+ if (GetVisualInfo(x11, egl, display, config) is { } visual)
+ candidates.Add((config, visual));
+ else
+ Logger.TryGet(LogEventLevel.Verbose, "OpenGL")
+ ?.Log(null, "EGL config {Config} has no native visual id", config);
+ }
+
+ // OrderByDescending is stable, so configs keep eglChooseConfig's relative order within each group.
+ foreach (var (config, visual) in candidates.OrderByDescending(c => c.visual.depth == 32))
+ {
+ if (ProbeConfig(x11, egl, display, config, visual))
+ return config;
+ }
+
+ return null;
+ }
+
+ private static bool ProbeConfig(X11Info x11, EglInterface egl, IntPtr display, IntPtr config,
+ XVisualInfo vi)
+ {
+ var colormap = XCreateColormap(x11.DeferredDisplay, x11.RootWindow, vi.visual, 0);
+ var attr = new XSetWindowAttributes
+ {
+ colormap = colormap,
+ border_pixel = IntPtr.Zero
+ };
+
+ var window = XCreateWindow(x11.DeferredDisplay, x11.RootWindow, 0, 0, 1, 1, 0,
+ (int)vi.depth, (int)CreateWindowArgs.InputOutput, vi.visual,
+ new UIntPtr((uint)(SetWindowValuemask.ColorMap | SetWindowValuemask.BorderPixel)), ref attr);
+
+ if (window == IntPtr.Zero)
+ {
+ XFreeColormap(x11.DeferredDisplay, colormap);
+ return false;
+ }
+
+ XFlush(x11.DeferredDisplay);
+
+ var surface = egl.CreateWindowSurface(display, config, window, new[] { EGL_NONE, EGL_NONE });
+ var success = surface != IntPtr.Zero;
+ if (success)
+ egl.DestroySurface(display, surface);
+
+ XDestroyWindow(x11.DeferredDisplay, window);
+ XFreeColormap(x11.DeferredDisplay, colormap);
+
+ Logger.TryGet(LogEventLevel.Verbose, "OpenGL")?.Log(null,
+ "EGL config {Config}: visualId={VisualId} depth={Depth} usable={Usable}",
+ config, vi.visualid, vi.depth, success);
+
+ return success;
+ }
+ }
+}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index 8b918aa27e..c043bf9bbf 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -227,13 +227,35 @@ namespace Avalonia.X11
if (renderingMode == X11RenderingMode.Egl)
{
- if (EglPlatformGraphics.TryCreate(()=>new EglDisplay(new EglDisplayCreationOptions()
+ if (EglPlatformGraphics.TryCreate(() =>
{
- SupportsContextSharing = true,
- SupportsMultipleContexts = true,
- GlVersions = opts.GlProfiles,
- Egl = new EglInterface()
- })) is { } egl)
+ var egl = new EglInterface();
+ var options = new EglDisplayCreationOptions
+ {
+ SupportsContextSharing = true,
+ SupportsMultipleContexts = true,
+ GlVersions = opts.GlProfiles,
+ Egl = egl,
+ // nvidia exposes multiple indistinguishable configs of which only some work,
+ // so we probe candidates by creating a throwaway window surface and pick a usable one.
+ ProbeConfig = (probeEgl, display, configs) =>
+ X11EglHelper.ChooseConfig(info, probeEgl, display, configs)
+ };
+
+ // nvidia requires the display to be created through the X11 platform extension,
+ // otherwise EGL_NATIVE_VISUAL_ID doesn't match the actual window visual.
+ var clientExtensions = egl.QueryString(IntPtr.Zero, EglConsts.EGL_EXTENSIONS);
+ if (egl.IsGetPlatformDisplayExtAvailable
+ && clientExtensions != null
+ && (clientExtensions.Contains("EGL_KHR_platform_x11")
+ || clientExtensions.Contains("EGL_EXT_platform_x11")))
+ {
+ options.PlatformType = EglConsts.EGL_PLATFORM_X11_EXT;
+ options.PlatformDisplay = info.DeferredDisplay;
+ }
+
+ return new EglDisplay(options);
+ }) is { } egl)
{
return egl;
}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 91fbfb93d2..67b97979cc 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -125,8 +125,9 @@ namespace Avalonia.X11
// OpenGL seems to be do weird things to it's current window which breaks resize sometimes
_useRenderWindow = glfeature != null;
-
+
var glx = glfeature as GlxPlatformGraphics;
+ var egl = glfeature as EglPlatformGraphics;
if (glx != null)
{
visualInfo = *glx.Display.VisualInfo;
@@ -134,11 +135,13 @@ namespace Avalonia.X11
// the target sufrace currently is
_useCompositorDrivenRenderWindowResize = true;
}
+ else if (egl != null)
+ {
+ visualInfo = X11EglHelper.GetVisualInfo(_x11, egl.Display);
+ }
else if (glfeature == null)
visualInfo = _x11.TransparentVisualInfo;
- var egl = glfeature as EglPlatformGraphics;
-
var visual = IntPtr.Zero;
var depth = 24;
if (visualInfo != null)
@@ -177,11 +180,17 @@ namespace Avalonia.X11
if (_useRenderWindow)
{
+ var renderValueMask = SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
+ SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore;
+ // A window with a non-default visual must be created with a matching colormap, otherwise X11
+ // raises BadMatch. This is required by nvidia when a custom visual is selected for the EGL config.
+ if (visualInfo != null)
+ renderValueMask |= SetWindowValuemask.ColorMap;
+
_renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, defaultWidth, defaultHeight, 0, depth,
(int)CreateWindowArgs.InputOutput,
visual,
- new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
- SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
+ new UIntPtr((uint)renderValueMask), ref attr);
}
else
{
@@ -281,6 +290,11 @@ namespace Avalonia.X11
});
platform.X11Screens.Changed += OnScreensChanged;
+
+ // The render surface (EGL/GLX) is created on the deferred display connection, which is a separate
+ // X11 connection from the one the windows were created on. Force a round-trip so the server has
+ // actually created the windows before anything on the other connection tries to use them.
+ XSync(_x11.Display, false);
}
private class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs
index 595e996733..0b663d7a1b 100644
--- a/src/Avalonia.X11/XLib.cs
+++ b/src/Avalonia.X11/XLib.cs
@@ -459,7 +459,27 @@ namespace Avalonia.X11
[DllImport(libX11)]
public static extern IntPtr XCreateColormap(IntPtr display, IntPtr window, IntPtr visual, int create);
-
+
+ [DllImport(libX11)]
+ public static extern int XFreeColormap(IntPtr display, IntPtr colormap);
+
+ public const long VisualIDMask = 0x1;
+
+ [DllImport(libX11)]
+ public static extern IntPtr XGetVisualInfo(IntPtr display, IntPtr vinfo_mask, ref XVisualInfo vinfo_template,
+ out int nitems);
+
+ public static unsafe XVisualInfo? XGetVisualInfoById(IntPtr display, IntPtr visualId)
+ {
+ var template = new XVisualInfo { visualid = visualId };
+ var ptr = XGetVisualInfo(display, new IntPtr(VisualIDMask), ref template, out var count);
+ if (ptr == IntPtr.Zero)
+ return null;
+ XVisualInfo? rv = count > 0 ? *(XVisualInfo*)ptr : null;
+ XFree(ptr);
+ return rv;
+ }
+
public enum XLookupStatus : uint
{
XBufferOverflow = 0xffffffffu,