Browse Source

Workarounds for NVIDIA EGL drivers (#21590)

* Workaround for nvidia EGL driver quirk with invalid draw/read/write buffers if surfaceless context was ever used

* X11: Fixed desktop EGL support for nvidia drivers
pull/21448/head
Nikita Tsukanov 4 months ago
committed by GitHub
parent
commit
06af9e8c95
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  2. 5
      src/Avalonia.OpenGL/Egl/EglConsts.cs
  3. 15
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  4. 9
      src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
  5. 28
      src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
  6. 15
      src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
  7. 9
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  8. 1
      src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
  9. 2
      src/Avalonia.OpenGL/GlConsts.cs
  10. 9
      src/Avalonia.OpenGL/GlInterface.cs
  11. 97
      src/Avalonia.X11/X11EglHelper.cs
  12. 34
      src/Avalonia.X11/X11Platform.cs
  13. 24
      src/Avalonia.X11/X11Window.cs
  14. 22
      src/Avalonia.X11/XLib.cs

4
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@ -10,6 +10,10 @@
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" />
<Import Project="..\..\build\TrimmingEnable.props" />

5
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;

15
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);

9
src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs

@ -3,6 +3,14 @@ using System.Collections.Generic;
namespace Avalonia.OpenGL.Egl;
/// <summary>
/// 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).
/// </summary>
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<bool>? DeviceLostCheckCallback { get; set; }
public Action? DisposeCallback { get; set; }
public IEnumerable<GlVersion>? GlVersions { get; set; }
public EglConfigProbeCallback? ProbeConfig { get; set; }
}
public class EglContextOptions

28
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<GlVersion>? 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<GlVersion>? 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);

15
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);
}

9
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);

1
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();

2
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;

9
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;

97
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
{
/// <summary>
/// Resolves the <see cref="XVisualInfo"/> 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).
/// </summary>
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);
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

34
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;
}

24
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

22
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,

Loading…
Cancel
Save