From 5bbbd5d6b25ac5d5062e9ab32cc3e122ac5b0419 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 18 May 2026 23:09:44 +0500 Subject: [PATCH] Support desktop OpenGL profiles via EGL (#21393) --- .../Pages/OpenGl/GlPageKnobs.xaml.cs | 2 +- .../ControlCatalog/Pages/OpenGlPage.xaml.cs | 1 + src/Avalonia.OpenGL/Egl/EglConsts.cs | 10 +-- src/Avalonia.OpenGL/Egl/EglContext.cs | 18 ++++- src/Avalonia.OpenGL/Egl/EglDisplay.cs | 1 + src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs | 78 +++++++++++++------ src/Avalonia.OpenGL/Egl/EglInterface.cs | 3 + src/Avalonia.X11/X11Platform.cs | 9 ++- .../OpenGl/Angle/AngleWin32EglDisplay.cs | 36 ++++----- 9 files changed, 104 insertions(+), 54 deletions(-) diff --git a/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml.cs b/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml.cs index a4e2964ba1..2daebe615a 100644 --- a/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGl/GlPageKnobs.xaml.cs @@ -65,6 +65,6 @@ public partial class GlPageKnobs : UserControl public string Info { get => _info; - private set => SetAndRaise(InfoProperty, ref _info, value); + set => SetAndRaise(InfoProperty, ref _info, value); } } \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index 51dc4c2c72..76a79290dd 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -76,6 +76,7 @@ namespace ControlCatalog.Pages _knobs.Yaw, _knobs.Pitch, _knobs.Roll, _knobs.Disco); if (_knobs.Disco > 0.01) RequestNextFrameRendering(); + _knobs?.Info = _content.Info; } } } diff --git a/src/Avalonia.OpenGL/Egl/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs index aff56ecfe0..b4fe8a99eb 100644 --- a/src/Avalonia.OpenGL/Egl/EglConsts.cs +++ b/src/Avalonia.OpenGL/Egl/EglConsts.cs @@ -125,17 +125,17 @@ namespace Avalonia.OpenGL.Egl // public const int EGL_MULTISAMPLE_RESOLVE = 0x3099; // public const int EGL_MULTISAMPLE_RESOLVE_DEFAULT = 0x309A; // public const int EGL_MULTISAMPLE_RESOLVE_BOX = 0x309B; -// public const int EGL_OPENGL_API = 0x30A2; -// public const int EGL_OPENGL_BIT = 0x0008; + public const int EGL_OPENGL_API = 0x30A2; + public const int EGL_OPENGL_BIT = 0x0008; // public const int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400; public const int EGL_CONTEXT_MAJOR_VERSION = 0x3098; public const int EGL_CONTEXT_MINOR_VERSION = 0x30FB; -// public const int EGL_CONTEXT_OPENGL_PROFILE_MASK = 0x30FD; + public const int EGL_CONTEXT_OPENGL_PROFILE_MASK = 0x30FD; // public const int EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY = 0x31BD; // public const int EGL_NO_RESET_NOTIFICATION = 0x31BE; // public const int EGL_LOSE_CONTEXT_ON_RESET = 0x31BF; -// public const int EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT = 0x00000001; -// public const int EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT = 0x00000002; + public const int EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT = 0x00000001; + public const int EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT = 0x00000002; // public const int EGL_CONTEXT_OPENGL_DEBUG = 0x31B0; // public const int EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE = 0x31B1; // public const int EGL_CONTEXT_OPENGL_ROBUST_ACCESS = 0x31B2; diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index ae729968ba..caf30e081d 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -13,6 +13,7 @@ namespace Avalonia.OpenGL.Egl private readonly EglDisplay _disp; private readonly EglInterface _egl; private readonly EglContext? _sharedWith; + private readonly int _api; private bool _isLost; private IntPtr _context; private readonly Action? _disposeCallback; @@ -26,6 +27,7 @@ namespace Avalonia.OpenGL.Egl _disp = display; _egl = egl; _sharedWith = sharedWith; + _api = display.Api; _context = ctx; _disposeCallback = disposeCallback; OffscreenSurface = offscreenSurface; @@ -56,21 +58,28 @@ namespace Avalonia.OpenGL.Egl private readonly object _l; private readonly IntPtr _display; private readonly IntPtr _context, _read, _draw; + private readonly int _api; public RestoreContext(EglInterface egl, IntPtr defDisplay, object l) { _egl = egl; _l = l; + _api = _egl.QueryApi(); _display = _egl.GetCurrentDisplay(); if (_display == IntPtr.Zero) + // Fallback display, it's invalid to call eglMakeCurrent without a display _display = defDisplay; - _context = _egl.GetCurrentContext(); - _read = _egl.GetCurrentSurface(EGL_READ); - _draw = _egl.GetCurrentSurface(EGL_DRAW); + else + { + _context = _egl.GetCurrentContext(); + _read = _egl.GetCurrentSurface(EGL_READ); + _draw = _egl.GetCurrentSurface(EGL_DRAW); + } } public void Dispose() { + _egl.BindApi(_api); _egl.MakeCurrent(_display, _draw, _read, _context); Monitor.Exit(_l); } @@ -90,6 +99,7 @@ namespace Avalonia.OpenGL.Egl { var old = new RestoreContext(_egl, _disp.Handle, _lock); var surf = surface ?? OffscreenSurface; + _egl.BindApi(_api); _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); if (!_egl.MakeCurrent(_disp.Handle, surf?.DangerousGetHandle() ?? IntPtr.Zero, surf?.DangerousGetHandle() ?? IntPtr.Zero, Context)) @@ -154,7 +164,7 @@ namespace Avalonia.OpenGL.Egl ShareWith = _sharedWith ?? this }); - public bool IsCurrent => _context != default && _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == _context; + public bool IsCurrent => _context != default && _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == _context && _egl.QueryApi() == _api; public void Dispose() { diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index 27a0bc065e..373aa0e964 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -49,6 +49,7 @@ namespace Avalonia.OpenGL.Egl } public EglInterface EglInterface => _egl; + internal int Api => _config.Api; public EglContext CreateContext(EglContextOptions? options) { if (SingleContext && _contexts.Any()) diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs index 0adf114b54..6ca7ab70c2 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs @@ -42,34 +42,64 @@ internal static class EglDisplayUtils }; var cfgs = versions - .Where(x => x.Type == GlProfileType.OpenGLES) .Select(x => { - var typeBit = EGL_OPENGL_ES3_BIT; - - switch (x.Major) + if (x.Type == GlProfileType.OpenGLES) { - case 2: - typeBit = EGL_OPENGL_ES2_BIT; - break; + var typeBit = EGL_OPENGL_ES3_BIT; - case 1: - typeBit = EGL_OPENGL_ES_BIT; - break; - } + switch (x.Major) + { + case 2: + typeBit = EGL_OPENGL_ES2_BIT; + break; + + case 1: + typeBit = EGL_OPENGL_ES_BIT; + break; + } - return new + return new + { + Attributes = new[] + { + EGL_CONTEXT_MAJOR_VERSION, x.Major, + EGL_CONTEXT_MINOR_VERSION, x.Minor, + EGL_NONE + }, + Api = EGL_OPENGL_ES_API, + RenderableTypeBit = typeBit, + Version = x + }; + } + else { - Attributes = new[] + var attrs = (x.Major > 3 || (x.Major == 3 && x.Minor >= 2)) + ? new[] + { + EGL_CONTEXT_MAJOR_VERSION, x.Major, + EGL_CONTEXT_MINOR_VERSION, x.Minor, + EGL_CONTEXT_OPENGL_PROFILE_MASK, + x.IsCompatibilityProfile + ? EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT + : EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_NONE + } + : new[] + { + EGL_CONTEXT_MAJOR_VERSION, x.Major, + EGL_CONTEXT_MINOR_VERSION, x.Minor, + EGL_NONE + }; + + return new { - EGL_CONTEXT_MAJOR_VERSION, x.Major, - EGL_CONTEXT_MINOR_VERSION, x.Minor, - EGL_NONE - }, - Api = EGL_OPENGL_ES_API, - RenderableTypeBit = typeBit, - Version = x - }; + Attributes = attrs, + Api = EGL_OPENGL_API, + RenderableTypeBit = EGL_OPENGL_BIT, + Version = x + }; + } }); foreach (var cfg in cfgs) @@ -101,7 +131,7 @@ internal static class EglDisplayUtils 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); + returnedStencilSize, cfg.Api); } } @@ -119,9 +149,10 @@ internal class EglConfigInfo public int[] Attributes { get; } public int SampleCount { get; } public int StencilSize { get; } + public int Api { get; } public EglConfigInfo(IntPtr config, GlVersion version, int surfaceType, int[] attributes, int sampleCount, - int stencilSize) + int stencilSize, int api) { Config = config; Version = version; @@ -129,5 +160,6 @@ internal class EglConfigInfo Attributes = attributes; SampleCount = sampleCount; StencilSize = stencilSize; + Api = api; } } diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index d158cb6543..a0210b544e 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -69,6 +69,9 @@ namespace Avalonia.OpenGL.Egl [GetProcAddress("eglBindAPI")] public partial bool BindApi(int api); + [GetProcAddress("eglQueryAPI")] + public partial int QueryApi(); + [GetProcAddress("eglChooseConfig")] public partial bool ChooseConfig(IntPtr display, int[] attribs, out IntPtr surfaceConfig, int numConfigs, out int choosenConfig); diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index a3f98265f4..8b918aa27e 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using Avalonia.Controls.Platform; using Avalonia.FreeDesktop; using Avalonia.FreeDesktop.AtSpi; @@ -226,7 +227,13 @@ namespace Avalonia.X11 if (renderingMode == X11RenderingMode.Egl) { - if (EglPlatformGraphics.TryCreate() is { } egl) + if (EglPlatformGraphics.TryCreate(()=>new EglDisplay(new EglDisplayCreationOptions() + { + SupportsContextSharing = true, + SupportsMultipleContexts = true, + GlVersions = opts.GlProfiles, + Egl = new EglInterface() + })) is { } egl) { return egl; } diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs index ac78557a35..9b23bc9aa8 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs @@ -21,17 +21,24 @@ namespace Avalonia.Win32.OpenGl.Angle protected override bool DisplayLockIsSharedWithContexts => true; + static EglDisplayOptions GetDisplayOptions(EglInterface egl, + Func? deviceLostCheckCallback = null, + Action? disposeCallback = null) => new() + { + Egl = egl, + ContextLossIsDisplayLoss = true, + GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles + .Where(x => x.Type == GlProfileType.OpenGLES), + DeviceLostCheckCallback = deviceLostCheckCallback, + DisposeCallback = disposeCallback + }; + public static AngleWin32EglDisplay CreateD3D9Display(EglInterface egl) { var display = egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, EGL_NONE }); - return new AngleWin32EglDisplay(display, egl, new EglDisplayOptions() - { - Egl = egl, - ContextLossIsDisplayLoss = true, - GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles - }, AngleOptions.PlatformApi.DirectX9); + return new AngleWin32EglDisplay(display, egl, GetDisplayOptions(egl), AngleOptions.PlatformApi.DirectX9); } public static AngleWin32EglDisplay CreateSharedD3D11Display(EglInterface egl) @@ -39,12 +46,7 @@ namespace Avalonia.Win32.OpenGl.Angle var display = egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_NONE }); - return new AngleWin32EglDisplay(display, egl, new EglDisplayOptions() - { - Egl = egl, - ContextLossIsDisplayLoss = true, - GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles - }, AngleOptions.PlatformApi.DirectX11); + return new AngleWin32EglDisplay(display, egl, GetDisplayOptions(egl), AngleOptions.PlatformApi.DirectX11); } public static unsafe AngleWin32EglDisplay CreateD3D11Display(Win32AngleEglInterface egl) @@ -155,14 +157,8 @@ namespace Avalonia.Win32.OpenGl.Angle var rv = new AngleWin32EglDisplay(display, egl, - new EglDisplayOptions - { - DisposeCallback = Cleanup, - Egl = egl, - ContextLossIsDisplayLoss = true, - DeviceLostCheckCallback = () => d3dDevice.DeviceRemovedReason != 0, - GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles - }, AngleOptions.PlatformApi.DirectX11); + GetDisplayOptions(egl, () => d3dDevice.DeviceRemovedReason != 0, Cleanup), + AngleOptions.PlatformApi.DirectX11); success = true; return rv; }