From 406373ac9e1cdf1ad9716d3963e6ae85a76b3a84 Mon Sep 17 00:00:00 2001 From: Shaojun Li Date: Thu, 10 Jul 2025 17:40:55 +0800 Subject: [PATCH 1/2] Fix window cannot be transparent when using EGL. --- src/Avalonia.X11/X11Window.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 0739f89829..9c85b477f5 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -130,8 +130,11 @@ namespace Avalonia.X11 // the target sufrace currently is _useCompositorDrivenRenderWindowResize = true; } - else if (glfeature == null) + else + { + // Egl or software (no glfeature) visualInfo = _x11.TransparentVisualInfo; + } var egl = glfeature as EglPlatformGraphics; From 375d2e17f792d0f7c1ad8c18991a2cd43b58c799 Mon Sep 17 00:00:00 2001 From: Shaojun Li Date: Tue, 22 Jul 2025 16:41:23 +0800 Subject: [PATCH 2/2] Use eglChooseConfig get all configs and find 32-bit depth X11 visuals. --- src/Avalonia.OpenGL/Egl/EglConsts.cs | 2 +- src/Avalonia.OpenGL/Egl/EglDisplay.cs | 5 +- src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs | 1 + src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs | 72 ++++++++++++------- src/Avalonia.OpenGL/Egl/EglInterface.cs | 4 +- .../Egl/EglPlatformGraphics.cs | 8 +-- src/Avalonia.X11/X11Platform.cs | 40 ++++++++++- src/Avalonia.X11/X11Structs.cs | 16 +++++ src/Avalonia.X11/X11Window.cs | 16 ++++- src/Avalonia.X11/XLib.cs | 3 + 10 files changed, 129 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.OpenGL/Egl/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs index aff56ecfe0..d930ed699b 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; diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index 27a0bc065e..32fbbbf30e 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -20,6 +20,7 @@ namespace Avalonia.OpenGL.Egl public IntPtr Handle => _display; public IntPtr Config => _config.Config; + public int NativeVisualId { get; } internal bool SingleContext => !_options.SupportsMultipleContexts; private readonly List _contexts = new(); @@ -45,7 +46,9 @@ namespace Avalonia.OpenGL.Egl if(_display == IntPtr.Zero) throw new ArgumentException(); - _config = EglDisplayUtils.InitializeAndGetConfig(_egl, display, options.GlVersions); + _config = EglDisplayUtils.InitializeAndGetConfig(_egl, display, options); + _egl.GetConfigAttrib(_display, _config.Config, EGL_NATIVE_VISUAL_ID, out var id); + NativeVisualId = id; } public EglInterface EglInterface => _egl; diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs index a2cbeb5b3d..d3c6586b4a 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs @@ -12,6 +12,7 @@ public class EglDisplayOptions public Func? DeviceLostCheckCallback { get; set; } public Action? DisposeCallback { get; set; } public IEnumerable? GlVersions { get; set; } + public Func? ChooseConfigCallback { get; set; } } public class EglContextOptions diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs index 0adf114b54..38bdac7a52 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs @@ -29,13 +29,13 @@ internal static class EglDisplayUtils return display; } - public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable? versions) + public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, EglDisplayOptions options) { if (!egl.Initialize(display, out _, out _)) throw OpenGlException.GetFormattedException("eglInitialize", egl); // TODO: AvaloniaLocator.Current.GetService()?.GlProfiles - versions ??= new[] + var versions = options.GlVersions ?? new[] { new GlVersion(GlProfileType.OpenGLES, 3, 0), new GlVersion(GlProfileType.OpenGLES, 2, 0) @@ -77,38 +77,58 @@ internal static class EglDisplayUtils if (!egl.BindApi(cfg.Api)) continue; foreach (var surfaceType in new[] { EGL_PBUFFER_BIT | EGL_WINDOW_BIT, EGL_WINDOW_BIT }) - foreach (var stencilSize in new[] { 8, 1, 0 }) - foreach (var depthSize in new[] { 8, 1, 0 }) { - var attribs = new[] + foreach (var stencilSize in new[] { 8, 1, 0 }) { - EGL_SURFACE_TYPE, surfaceType, - EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_STENCIL_SIZE, stencilSize, - EGL_DEPTH_SIZE, depthSize, - EGL_NONE - }; - if (!egl.ChooseConfig(display, attribs, out var config, 1, out int numConfigs)) - continue; - if (numConfigs == 0) - continue; - - - egl.GetConfigAttrib(display, config, EGL_SAMPLES, out var sampleCount); - egl.GetConfigAttrib(display, config, EGL_STENCIL_SIZE, out var returnedStencilSize); - return new EglConfigInfo(config, cfg.Version, surfaceType, cfg.Attributes, sampleCount, - returnedStencilSize); + foreach (var depthSize in new[] { 8, 1, 0 }) + { + var attribs = new[] + { + EGL_SURFACE_TYPE, surfaceType, + EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_STENCIL_SIZE, stencilSize, + EGL_DEPTH_SIZE, depthSize, + EGL_NONE + }; + if (!egl.ChooseConfig(display, attribs, null, 0, out int numConfigs)) + continue; + if (numConfigs == 0) + continue; + + IntPtr config; + var configs = new IntPtr[numConfigs]; + if (!egl.ChooseConfig(display, attribs, configs, numConfigs, out numConfigs)) + continue; + + if (options.ChooseConfigCallback != null) + { + config = options.ChooseConfigCallback(egl, display, configs); + } + else + { + config = configs[0]; + } + + if (config == IntPtr.Zero) + continue; + + egl.GetConfigAttrib(display, config, EGL_SAMPLES, out var sampleCount); + egl.GetConfigAttrib(display, config, EGL_STENCIL_SIZE, out var returnedStencilSize); + return new EglConfigInfo(config, cfg.Version, surfaceType, cfg.Attributes, sampleCount, + returnedStencilSize); + } + } } } throw new OpenGlException("No suitable EGL config was found"); } - + } internal class EglConfigInfo diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index 5c93d2084b..e0099779fb 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -64,8 +64,8 @@ namespace Avalonia.OpenGL.Egl [GetProcAddress("eglChooseConfig")] public partial bool ChooseConfig(IntPtr display, int[] attribs, - out IntPtr surfaceConfig, int numConfigs, out int choosenConfig); - + IntPtr[]? configs, int numConfigs, out int choosenConfig); + [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..432c83d078 100644 --- a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs +++ b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs @@ -6,14 +6,14 @@ namespace Avalonia.OpenGL.Egl { public sealed class EglPlatformGraphics : IPlatformGraphics { - private readonly EglDisplay _display; + public EglDisplay Display { get; } public bool UsesSharedContext => false; - public IPlatformGraphicsContext CreateContext() => _display.CreateContext(null); + public IPlatformGraphicsContext CreateContext() => Display.CreateContext(null); public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException(); public EglPlatformGraphics(EglDisplay display) { - _display = display; + Display = display; } public static EglPlatformGraphics? TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions @@ -23,7 +23,7 @@ namespace Avalonia.OpenGL.Egl SupportsMultipleContexts = true, SupportsContextSharing = true })); - + public static EglPlatformGraphics? TryCreate(Func displayFactory) { try diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 0e89a46332..b09a02b507 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -209,7 +209,45 @@ namespace Avalonia.X11 if (renderingMode == X11RenderingMode.Egl) { - if (EglPlatformGraphics.TryCreate() is { } egl) + if (EglPlatformGraphics.TryCreate(() => new EglDisplay(new EglDisplayCreationOptions + { + Egl = new EglInterface(), + SupportsMultipleContexts = true, + SupportsContextSharing = true, + ChooseConfigCallback = (egl, display, configs) => + { + if (configs.Length == 0) + { + return default; + } + XVisualInfo template = new XVisualInfo(); + for (int i = 0; i < configs.Length; i++) + { + egl.GetConfigAttrib(display, configs[i], EglConsts.EGL_NATIVE_VISUAL_ID, out var visualId); + template.visualid = (nint)visualId; + var visualInfoPtr = XGetVisualInfo(info.Display, VisualInfoMasks.VisualIDMask, template, out var nitems); + if (nitems > 0 && visualInfoPtr != IntPtr.Zero) + { + unsafe + { + try + { + if (((XVisualInfo*)visualInfoPtr)->depth == 32) + { + // Prefer to use 32bit depth. + return configs[i]; + } + } + finally + { + XFree(visualInfoPtr); + } + } + } + } + return configs.First(); + }, + })) is { } egl) { return egl; } diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 1682ad4bb5..3ccf7c1dd4 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1786,6 +1786,22 @@ namespace Avalonia.X11 { public ulong blue_mask; private fixed byte funcs[128]; } + + [Flags] + internal enum VisualInfoMasks : long + { + VisualNoMask = 0x0, + VisualIDMask = 0x1, + VisualScreenMask = 0x2, + VisualDepthMask = 0x4, + VisualClassMask = 0x8, + VisualRedMaskMask = 0x10, + VisualGreenMaskMask = 0x20, + VisualBlueMaskMask = 0x40, + VisualColormapSizeMask = 0x80, + VisualBitsPerRGBMask = 0x100, + VisualAllMask = 0x1FF, + } [StructLayout(LayoutKind.Sequential)] internal struct XVisualInfo diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 9c85b477f5..81bf0c7122 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -121,8 +121,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; @@ -130,14 +131,23 @@ namespace Avalonia.X11 // the target sufrace currently is _useCompositorDrivenRenderWindowResize = true; } + else if (egl != null) + { + XVisualInfo template = new XVisualInfo(); + template.visualid = (nint)egl.Display.NativeVisualId; + var visualInfoPtr = XGetVisualInfo(_x11.Display, VisualInfoMasks.VisualIDMask, template, out var nitems); + if (nitems > 0 && visualInfoPtr != IntPtr.Zero) + { + visualInfo = *(XVisualInfo*)visualInfoPtr; + XFree(visualInfoPtr); + } + } else { // Egl or software (no glfeature) visualInfo = _x11.TransparentVisualInfo; } - var egl = glfeature as EglPlatformGraphics; - var visual = IntPtr.Zero; var depth = 24; if (visualInfo != null) diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index cfd3a03c8f..a515f1c3fc 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -749,5 +749,8 @@ namespace Avalonia.X11 public static int XkbSetGroupForCoreState(int state, int newGroup) => (state & ~(0x3 << 13)) | ((newGroup & 0x3) << 13); + + [DllImport(libX11)] + public static extern IntPtr XGetVisualInfo(IntPtr display, VisualInfoMasks vinfo_mask, in XVisualInfo vinfo_template, out int nitems_return); } }