From ed9dff6b8f4006c6da2f056019c7bc60117b76fd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Jan 2019 18:55:50 +0300 Subject: [PATCH] [X11] Use GLX and transparent visuals when possible --- src/Avalonia.OpenGL/EglDisplay.cs | 12 +-- src/Avalonia.OpenGL/EglInterface.cs | 28 +++--- ...tAttribute.cs => GlEntryPointAttribute.cs} | 4 +- src/Avalonia.OpenGL/GlInterface.cs | 35 +++++-- src/Avalonia.OpenGL/GlInterfaceBase.cs | 2 +- src/Avalonia.X11/Glx/Glx.cs | 55 +++++++++++ src/Avalonia.X11/Glx/GlxConsts.cs | 97 ++++++++++++++++++ src/Avalonia.X11/Glx/GlxContext.cs | 34 +++++++ src/Avalonia.X11/Glx/GlxDisplay.cs | 98 +++++++++++++++++++ src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs | 86 ++++++++++++++++ src/Avalonia.X11/Glx/GlxPlatformFeature.cs | 44 +++++++++ src/Avalonia.X11/X11FramebufferSurface.cs | 6 +- src/Avalonia.X11/X11Info.cs | 8 +- src/Avalonia.X11/X11Platform.cs | 24 ++++- src/Avalonia.X11/X11Window.cs | 75 ++++++++++---- .../Avalonia.Skia/PlatformRenderInterface.cs | 3 +- 16 files changed, 548 insertions(+), 63 deletions(-) rename src/Avalonia.OpenGL/{EntryPointAttribute.cs => GlEntryPointAttribute.cs} (63%) create mode 100644 src/Avalonia.X11/Glx/Glx.cs create mode 100644 src/Avalonia.X11/Glx/GlxConsts.cs create mode 100644 src/Avalonia.X11/Glx/GlxContext.cs create mode 100644 src/Avalonia.X11/Glx/GlxDisplay.cs create mode 100644 src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs create mode 100644 src/Avalonia.X11/Glx/GlxPlatformFeature.cs diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index 2a75e9458e..b14932acfe 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -86,18 +86,8 @@ namespace Avalonia.OpenGL if (_contextAttributes == null) throw new OpenGlException("No suitable EGL config was found"); - - GlInterface = new GlInterface((proc, optional) => - { - using (var u = new Utf8Buffer(proc)) - { - var rv = _egl.GetProcAddress(u); - if (rv == IntPtr.Zero && !optional) - throw new OpenGlException("Missing function " + proc); - return rv; - } - }); + GlInterface = GlInterface.FromNativeUtf8GetProcAddress(b => _egl.GetProcAddress(b)); } public EglDisplay() : this(new EglInterface()) diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs index c41a01340c..00fcd97af0 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -34,61 +34,61 @@ namespace Avalonia.OpenGL // ReSharper disable UnassignedGetOnlyAutoProperty public delegate int EglGetError(); - [EntryPoint("eglGetError")] + [GlEntryPoint("eglGetError")] public EglGetError GetError { get; } public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay); - [EntryPoint("eglGetDisplay")] + [GlEntryPoint("eglGetDisplay")] public EglGetDisplay GetDisplay { get; } public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs); - [EntryPoint("eglGetPlatformDisplayEXT", true)] + [GlEntryPoint("eglGetPlatformDisplayEXT", true)] public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; } public delegate bool EglInitialize(IntPtr display, out int major, out int minor); - [EntryPoint("eglInitialize")] + [GlEntryPoint("eglInitialize")] public EglInitialize Initialize { get; } public delegate IntPtr EglGetProcAddress(Utf8Buffer proc); - [EntryPoint("eglGetProcAddress")] + [GlEntryPoint("eglGetProcAddress")] public EglGetProcAddress GetProcAddress { get; } public delegate bool EglBindApi(int api); - [EntryPoint("eglBindAPI")] + [GlEntryPoint("eglBindAPI")] public EglBindApi BindApi { get; } public delegate bool EglChooseConfig(IntPtr display, int[] attribs, out IntPtr surfaceConfig, int numConfigs, out int choosenConfig); - [EntryPoint("eglChooseConfig")] + [GlEntryPoint("eglChooseConfig")] public EglChooseConfig ChooseConfig { get; } public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config, IntPtr share, int[] attrs); - [EntryPoint("eglCreateContext")] + [GlEntryPoint("eglCreateContext")] public EglCreateContext CreateContext { get; } public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs); - [EntryPoint("eglCreatePbufferSurface")] + [GlEntryPoint("eglCreatePbufferSurface")] public EglCreatePBufferSurface CreatePBufferSurface { get; } public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); - [EntryPoint("eglMakeCurrent")] + [GlEntryPoint("eglMakeCurrent")] public EglMakeCurrent MakeCurrent { get; } public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface); - [EntryPoint("eglDestroySurface")] + [GlEntryPoint("eglDestroySurface")] public EglDisplaySurfaceVoidDelegate DestroySurface { get; } - [EntryPoint("eglSwapBuffers")] + [GlEntryPoint("eglSwapBuffers")] public EglDisplaySurfaceVoidDelegate SwapBuffers { get; } public delegate IntPtr EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs); - [EntryPoint("eglCreateWindowSurface")] + [GlEntryPoint("eglCreateWindowSurface")] public EglCreateWindowSurface CreateWindowSurface { get; } public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv); - [EntryPoint("eglGetConfigAttrib")] + [GlEntryPoint("eglGetConfigAttrib")] public EglGetConfigAttrib GetConfigAttrib { get; } // ReSharper restore UnassignedGetOnlyAutoProperty diff --git a/src/Avalonia.OpenGL/EntryPointAttribute.cs b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs similarity index 63% rename from src/Avalonia.OpenGL/EntryPointAttribute.cs rename to src/Avalonia.OpenGL/GlEntryPointAttribute.cs index 241f517df9..fbde8e57be 100644 --- a/src/Avalonia.OpenGL/EntryPointAttribute.cs +++ b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs @@ -2,12 +2,12 @@ using System; namespace Avalonia.OpenGL { - class EntryPointAttribute : Attribute + public class GlEntryPointAttribute : Attribute { public string EntryPoint { get; } public bool Optional { get; } - public EntryPointAttribute(string entryPoint, bool optional = false) + public GlEntryPointAttribute(string entryPoint, bool optional = false) { EntryPoint = entryPoint; Optional = optional; diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index f23e1c5829..7921989c04 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.Platform.Interop; namespace Avalonia.OpenGL { @@ -8,42 +9,62 @@ namespace Avalonia.OpenGL public class GlInterface : GlInterfaceBase { private readonly Func _getProcAddress; + public string Version { get; } public GlInterface(Func getProcAddress) : base(getProcAddress) { _getProcAddress = getProcAddress; + var versionPtr = GetString(GlConsts.GL_VERSION); + if (versionPtr != IntPtr.Zero) + Version = Marshal.PtrToStringAnsi(versionPtr); } + public static GlInterface FromNativeUtf8GetProcAddress(Func getProcAddress) => + new GlInterface((proc, optional) => + { + using (var u = new Utf8Buffer(proc)) + { + var rv = getProcAddress(u); + if (rv == IntPtr.Zero && !optional) + throw new OpenGlException("Missing function " + proc); + return rv; + } + }); + public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true); public T GetProcAddress(string proc) => Marshal.GetDelegateForFunctionPointer(GetProcAddress(proc)); // ReSharper disable UnassignedGetOnlyAutoProperty public delegate int GlGetError(); - [EntryPoint("glGetError")] + [GlEntryPoint("glGetError")] public GlGetError GetError { get; } public delegate void GlClearStencil(int s); - [EntryPoint("glClearStencil")] + [GlEntryPoint("glClearStencil")] public GlClearStencil ClearStencil { get; } public delegate void GlClearColor(int r, int g, int b, int a); - [EntryPoint("glClearColor")] + [GlEntryPoint("glClearColor")] public GlClearColor ClearColor { get; } public delegate void GlClear(int bits); - [EntryPoint("glClear")] + [GlEntryPoint("glClear")] public GlClear Clear { get; } public delegate void GlViewport(int x, int y, int width, int height); - [EntryPoint("glViewport")] + [GlEntryPoint("glViewport")] public GlViewport Viewport { get; } - [EntryPoint("glFlush")] + [GlEntryPoint("glFlush")] public Action Flush { get; } + public delegate IntPtr GlGetString(int v); + [GlEntryPoint("glGetString")] + public GlGetString GetString { get; } + public delegate void GlGetIntegerv(int name, out int rv); - [EntryPoint("glGetIntegerv")] + [GlEntryPoint("glGetIntegerv")] public GlGetIntegerv GetIntegerv { get; } // ReSharper restore UnassignedGetOnlyAutoProperty diff --git a/src/Avalonia.OpenGL/GlInterfaceBase.cs b/src/Avalonia.OpenGL/GlInterfaceBase.cs index 33191a6567..8b3eb2797d 100644 --- a/src/Avalonia.OpenGL/GlInterfaceBase.cs +++ b/src/Avalonia.OpenGL/GlInterfaceBase.cs @@ -10,7 +10,7 @@ namespace Avalonia.OpenGL { foreach (var prop in this.GetType().GetProperties()) { - var a = prop.GetCustomAttribute(); + var a = prop.GetCustomAttribute(); if (a != null) { var fieldName = $"<{prop.Name}>k__BackingField"; diff --git a/src/Avalonia.X11/Glx/Glx.cs b/src/Avalonia.X11/Glx/Glx.cs new file mode 100644 index 0000000000..2382d66a84 --- /dev/null +++ b/src/Avalonia.X11/Glx/Glx.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using Avalonia.Platform.Interop; + +namespace Avalonia.X11.Glx +{ + static unsafe class Glx + { + private const string libGL = "libGL.so.1"; + [DllImport(libGL, EntryPoint = "glXMakeCurrent")] + public static extern bool GlxMakeCurrent(IntPtr display, IntPtr drawable, IntPtr context); + + + [DllImport(libGL, EntryPoint = "glXChooseVisual")] + public static extern XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList); + + [DllImport(libGL, EntryPoint = "glXCreateContext")] + public static extern IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct); + + + [DllImport(libGL, EntryPoint = "glXGetProcAddress")] + public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer); + + [DllImport(libGL, EntryPoint = "glXDestroyContext")] + public static extern void GlxDestroyContext(IntPtr dpy, IntPtr ctx); + + + [DllImport(libGL, EntryPoint = "glXChooseFBConfig")] + public static extern IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements); + + public static IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable attribs, out int nelements) + { + var arr = attribs.Concat(new[]{0}).ToArray(); + return GlxChooseFBConfig(dpy, screen, arr, out nelements); + } + [DllImport(libGL, EntryPoint = "glXGetVisualFromFBConfig")] + public static extern XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config); + + + [DllImport(libGL, EntryPoint = "glXGetFBConfigAttrib")] + public static extern int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value); + + + [DllImport(libGL, EntryPoint = "glXSwapBuffers")] + public static extern void GlxSwapBuffers(IntPtr dpy, IntPtr drawable); + + [DllImport(libGL, EntryPoint = "glXWaitX")] + public static extern void GlxWaitX(); + + [DllImport(libGL, EntryPoint = "glXWaitGL")] + public static extern void GlxWaitGL(); + } +} diff --git a/src/Avalonia.X11/Glx/GlxConsts.cs b/src/Avalonia.X11/Glx/GlxConsts.cs new file mode 100644 index 0000000000..71f6636371 --- /dev/null +++ b/src/Avalonia.X11/Glx/GlxConsts.cs @@ -0,0 +1,97 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo +// ReSharper disable UnusedMember.Global +#pragma warning disable 414 +namespace Avalonia.X11.Glx +{ + class GlxConsts + { + public const int GLX_USE_GL = 1; + public const int GLX_BUFFER_SIZE = 2; + public const int GLX_LEVEL = 3; + public const int GLX_RGBA = 4; + public const int GLX_DOUBLEBUFFER = 5; + public const int GLX_STEREO = 6; + public const int GLX_AUX_BUFFERS = 7; + public const int GLX_RED_SIZE = 8; + public const int GLX_GREEN_SIZE = 9; + public const int GLX_BLUE_SIZE = 10; + public const int GLX_ALPHA_SIZE = 11; + public const int GLX_DEPTH_SIZE = 12; + public const int GLX_STENCIL_SIZE = 13; + public const int GLX_ACCUM_RED_SIZE = 14; + public const int GLX_ACCUM_GREEN_SIZE = 15; + public const int GLX_ACCUM_BLUE_SIZE = 16; + public const int GLX_ACCUM_ALPHA_SIZE = 17; + public const int GLX_BAD_SCREEN = 1; + public const int GLX_BAD_ATTRIBUTE = 2; + public const int GLX_NO_EXTENSION = 3; + public const int GLX_BAD_VISUAL = 4; + public const int GLX_BAD_CONTEXT = 5; + public const int GLX_BAD_VALUE = 6; + public const int GLX_BAD_ENUM = 7; + public const int GLX_VENDOR = 1; + public const int GLX_VERSION = 2; + public const int GLX_EXTENSIONS= 3; + public const int GLX_CONFIG_CAVEAT = 0x20; + public const int GLX_DONT_CARE = unchecked((int)0xFFFFFFFF); + public const int GLX_X_VISUAL_TYPE = 0x22; + public const int GLX_TRANSPARENT_TYPE = 0x23; + public const int GLX_TRANSPARENT_INDEX_VALUE = 0x24; + public const int GLX_TRANSPARENT_RED_VALUE = 0x25; + public const int GLX_TRANSPARENT_GREEN_VALUE = 0x26; + public const int GLX_TRANSPARENT_BLUE_VALUE = 0x27; + public const int GLX_TRANSPARENT_ALPHA_VALUE = 0x28; + public const int GLX_WINDOW_BIT = 0x00000001; + public const int GLX_PIXMAP_BIT = 0x00000002; + public const int GLX_PBUFFER_BIT = 0x00000004; + public const int GLX_AUX_BUFFERS_BIT = 0x00000010; + public const int GLX_FRONT_LEFT_BUFFER_BIT = 0x00000001; + public const int GLX_FRONT_RIGHT_BUFFER_BIT = 0x00000002; + public const int GLX_BACK_LEFT_BUFFER_BIT = 0x00000004; + public const int GLX_BACK_RIGHT_BUFFER_BIT = 0x00000008; + public const int GLX_DEPTH_BUFFER_BIT = 0x00000020; + public const int GLX_STENCIL_BUFFER_BIT = 0x00000040; + public const int GLX_ACCUM_BUFFER_BIT = 0x00000080; + public const int GLX_NONE = 0x8000; + public const int GLX_SLOW_CONFIG = 0x8001; + public const int GLX_TRUE_COLOR = 0x8002; + public const int GLX_DIRECT_COLOR = 0x8003; + public const int GLX_PSEUDO_COLOR = 0x8004; + public const int GLX_STATIC_COLOR = 0x8005; + public const int GLX_GRAY_SCALE = 0x8006; + public const int GLX_STATIC_GRAY = 0x8007; + public const int GLX_TRANSPARENT_RGB = 0x8008; + public const int GLX_TRANSPARENT_INDEX = 0x8009; + public const int GLX_VISUAL_ID = 0x800B; + public const int GLX_SCREEN = 0x800C; + public const int GLX_NON_CONFORMANT_CONFIG = 0x800D; + public const int GLX_DRAWABLE_TYPE = 0x8010; + public const int GLX_RENDER_TYPE = 0x8011; + public const int GLX_X_RENDERABLE = 0x8012; + public const int GLX_FBCONFIG_ID = 0x8013; + public const int GLX_RGBA_TYPE = 0x8014; + public const int GLX_COLOR_INDEX_TYPE = 0x8015; + public const int GLX_MAX_PBUFFER_WIDTH = 0x8016; + public const int GLX_MAX_PBUFFER_HEIGHT = 0x8017; + public const int GLX_MAX_PBUFFER_PIXELS = 0x8018; + public const int GLX_PRESERVED_CONTENTS = 0x801B; + public const int GLX_LARGEST_PBUFFER = 0x801C; + public const int GLX_WIDTH = 0x801D; + public const int GLX_HEIGHT = 0x801E; + public const int GLX_EVENT_MASK = 0x801F; + public const int GLX_DAMAGED = 0x8020; + public const int GLX_SAVED = 0x8021; + public const int GLX_WINDOW = 0x8022; + public const int GLX_PBUFFER = 0x8023; + public const int GLX_PBUFFER_HEIGHT = 0x8040; + public const int GLX_PBUFFER_WIDTH = 0x8041; + public const int GLX_RGBA_BIT = 0x00000001; + public const int GLX_COLOR_INDEX_BIT = 0x00000002; + public const int GLX_PBUFFER_CLOBBER_MASK = 0x08000000; + public const int GLX_SAMPLE_BUFFERS = 0x186a0 /*100000*/; + public const int GLX_SAMPLES = 0x186a1 /*100001*/; + public const int GLX_PbufferClobber = 0; + public const int GLX_BufferSwapComplete = 1; + } +} diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs new file mode 100644 index 0000000000..b263d77666 --- /dev/null +++ b/src/Avalonia.X11/Glx/GlxContext.cs @@ -0,0 +1,34 @@ +using System; +using System.Reactive.Disposables; +using System.Threading; +using Avalonia.OpenGL; +using static Avalonia.X11.Glx.Glx; +namespace Avalonia.X11.Glx +{ + class GlxContext : IGlContext + { + public IntPtr Handle { get; } + private readonly X11Info _x11; + private readonly object _lock = new object(); + + public GlxContext(IntPtr handle, GlxDisplay display, X11Info x11) + { + Handle = handle; + _x11 = x11; + Display = display; + } + + public GlxDisplay Display { get; } + IGlDisplay IGlContext.Display => Display; + + public IDisposable Lock() + { + Monitor.Enter(_lock); + return Disposable.Create(() => Monitor.Exit(_lock)); + } + + public void MakeCurrent() => MakeCurrent(IntPtr.Zero); + + public void MakeCurrent(IntPtr xid) => GlxMakeCurrent(_x11.Display, xid, Handle); + } +} diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs new file mode 100644 index 0000000000..a94f2ba625 --- /dev/null +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using Avalonia.OpenGL; +using static Avalonia.X11.Glx.GlxConsts; +using static Avalonia.X11.Glx.Glx; +namespace Avalonia.X11.Glx +{ + unsafe class GlxDisplay : IGlDisplay + { + private readonly X11Info _x11; + private readonly IntPtr _fbconfig; + private readonly XVisualInfo* _visual; + public GlDisplayType Type => GlDisplayType.OpenGL2; + public GlInterface GlInterface { get; } + + public XVisualInfo* VisualInfo => _visual; + public int SampleCount { get; } + public int StencilSize { get; } + + public GlxContext ImmediateContext { get; } + public GlxContext DeferredContext { get; } + + public GlxDisplay(X11Info x11) + { + _x11 = x11; + + var baseAttribs = new[] + { + GLX_X_RENDERABLE, 1, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DOUBLEBUFFER, 1, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_ALPHA_SIZE, 8, + GLX_DEPTH_SIZE, 1, + GLX_STENCIL_SIZE, 8, + + }; + + foreach (var attribs in new[] + { + //baseAttribs.Concat(multiattribs), + baseAttribs, + }) + { + var ptr = GlxChooseFBConfig(_x11.Display, x11.DefaultScreen, + attribs, out var count); + for (var c = 0 ; c < count; c++) + { + + var visual = GlxGetVisualFromFBConfig(_x11.Display, ptr[c]); + // We prefer 32 bit visuals + if (_fbconfig == IntPtr.Zero || visual->depth == 32) + { + _fbconfig = ptr[c]; + _visual = visual; + if(visual->depth == 32) + break; + } + } + + if (_fbconfig != IntPtr.Zero) + break; + } + + if (_fbconfig == IntPtr.Zero) + throw new OpenGlException("Unable to choose FBConfig"); + + if (_visual == null) + throw new OpenGlException("Unable to get visual info from FBConfig"); + if (GlxGetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0) + SampleCount = samples; + if (GlxGetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0) + StencilSize = stencil; + + ImmediateContext = CreateContext(null); + DeferredContext = CreateContext(ImmediateContext); + ImmediateContext.MakeCurrent(); + + GlInterface = GlInterface.FromNativeUtf8GetProcAddress(p => GlxGetProcAddress(p)); + } + + public void ClearContext() => GlxMakeCurrent(_x11.Display, IntPtr.Zero, IntPtr.Zero); + + public GlxContext CreateContext(IGlContext share) + { + var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero; + var h = GlxCreateContext(_x11.Display, _visual, sharelist, true); + if (h == IntPtr.Zero) + throw new OpenGlException("Unable to create direct GLX context"); + return new GlxContext(h, this, _x11); + } + + public void SwapBuffers(IntPtr xid) => GlxSwapBuffers(_x11.Display, xid); + } +} diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs new file mode 100644 index 0000000000..f6a4090dc0 --- /dev/null +++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs @@ -0,0 +1,86 @@ +using System; +using Avalonia.OpenGL; + +namespace Avalonia.X11.Glx +{ + class GlxGlPlatformSurface: IGlPlatformSurface + { + + private readonly GlxDisplay _display; + private readonly GlxContext _context; + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; + + public GlxGlPlatformSurface(GlxDisplay display, GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) + { + _display = display; + _context = context; + _info = info; + } + + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + { + return new RenderTarget(_context, _info); + } + + class RenderTarget : IGlPlatformSurfaceRenderTarget + { + private readonly GlxContext _context; + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; + + public RenderTarget(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) + { + _context = context; + _info = info; + } + + public void Dispose() + { + // No-op + } + + public IGlPlatformSurfaceRenderingSession BeginDraw() + { + var l = _context.Lock(); + try + { + _context.MakeCurrent(_info.Handle); + return new Session(_context, _info, l); + } + catch + { + l.Dispose(); + throw; + } + } + + class Session : IGlPlatformSurfaceRenderingSession + { + private readonly GlxContext _context; + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; + private IDisposable _lock; + + public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info, + IDisposable @lock) + { + _context = context; + _info = info; + _lock = @lock; + } + + public void Dispose() + { + _context.Display.GlInterface.Flush(); + Glx.GlxWaitGL(); + _context.Display.SwapBuffers(_info.Handle); + Glx.GlxWaitX(); + _context.Display.ClearContext(); + _lock.Dispose(); + } + + public IGlDisplay Display => _context.Display; + public PixelSize Size => _info.Size; + public double Scaling => _info.Scaling; + } + } + } +} diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs new file mode 100644 index 0000000000..d15b5fe4b8 --- /dev/null +++ b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs @@ -0,0 +1,44 @@ +using System; +using Avalonia.Logging; +using Avalonia.OpenGL; + +namespace Avalonia.X11.Glx +{ + class GlxGlPlatformFeature : IWindowingPlatformGlFeature + { + public GlxDisplay Display { get; private set; } + public IGlContext ImmediateContext { get; private set; } + public GlxContext DeferredContext { get; private set; } + + public static bool TryInitialize(X11Info x11) + { + var feature = TryCreate(x11); + if (feature != null) + { + AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); + return true; + } + + return false; + } + + public static GlxGlPlatformFeature TryCreate(X11Info x11) + { + try + { + var disp = new GlxDisplay(x11); + return new GlxGlPlatformFeature + { + Display = disp, + ImmediateContext = disp.ImmediateContext, + DeferredContext = disp.DeferredContext + }; + } + catch(Exception e) + { + Logger.Error("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e); + return null; + } + } + } +} diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs index 70220cebcd..07c88b51a8 100644 --- a/src/Avalonia.X11/X11FramebufferSurface.cs +++ b/src/Avalonia.X11/X11FramebufferSurface.cs @@ -8,12 +8,14 @@ namespace Avalonia.X11 { private readonly IntPtr _display; private readonly IntPtr _xid; + private readonly int _depth; private readonly Func _scaling; - public X11FramebufferSurface(IntPtr display, IntPtr xid, Func scaling) + public X11FramebufferSurface(IntPtr display, IntPtr xid, int depth, Func scaling) { _display = display; _xid = xid; + _depth = depth; _scaling = scaling; } @@ -23,7 +25,7 @@ namespace Avalonia.X11 XGetGeometry(_display, _xid, out var root, out var x, out var y, out var width, out var height, out var bw, out var d); XUnlockDisplay(_display); - return new X11Framebuffer(_display, _xid, 24,width, height, _scaling()); + return new X11Framebuffer(_display, _xid, _depth, width, height, _scaling()); } } } diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 6e4b31fb5c..8cfcbb9836 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -7,7 +7,7 @@ using static Avalonia.X11.XLib; // ReSharper disable UnusedAutoPropertyAccessor.Local namespace Avalonia.X11 { - class X11Info + unsafe class X11Info { public IntPtr Display { get; } public IntPtr DeferredDisplay { get; } @@ -31,6 +31,7 @@ namespace Avalonia.X11 public Version XInputVersion { get; } public IntPtr LastActivityTimestamp { get; set; } + public XVisualInfo? TransparentVisualInfo { get; set; } public unsafe X11Info(IntPtr display, IntPtr deferredDisplay) { @@ -45,7 +46,10 @@ namespace Avalonia.X11 //TODO: Open an actual XIM once we get support for preedit in our textbox XSetLocaleModifiers("@im=none"); Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - + XMatchVisualInfo(Display, DefaultScreen, 32, 4, out var visual); + if (visual.depth == 32) + TransparentVisualInfo = visual; + try { if (XRRQueryExtension(display, out int randrEventBase, out var randrErrorBase) != 0) diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 3b50dff5c8..323411e319 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -9,6 +9,7 @@ using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.X11; +using Avalonia.X11.Glx; using static Avalonia.X11.XLib; namespace Avalonia.X11 { @@ -23,7 +24,7 @@ namespace Avalonia.X11 public X11Info Info { get; private set; } public IX11Screens X11Screens { get; private set; } public IScreenImpl Screens { get; private set; } - public void Initialize() + public void Initialize(X11PlatformOptions options) { XInitThreads(); Display = XOpenDisplay(IntPtr.Zero); @@ -54,8 +55,14 @@ namespace Avalonia.X11 if (xi2.Init(this)) XI2 = xi2; } - EglGlPlatformFeature.TryInitialize(); + if (options.UseGpu) + { + if (options.UseEGL) + EglGlPlatformFeature.TryInitialize(); + else + GlxGlPlatformFeature.TryInitialize(Info); + } } public IntPtr DeferredDisplay { get; set; } @@ -79,15 +86,22 @@ namespace Avalonia.X11 namespace Avalonia { + + public class X11PlatformOptions + { + public bool UseEGL { get; set; } + public bool UseGpu { get; set; } = true; + } public static class AvaloniaX11PlatformExtensions { - public static T UseX11(this T builder) where T : AppBuilderBase, new() + public static T UseX11(this T builder, X11PlatformOptions options = null) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize()); + builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize(options ?? new X11PlatformOptions())); return builder; } - public static void InitializeX11Platform() => new AvaloniaX11Platform().Initialize(); + public static void InitializeX11Platform(X11PlatformOptions options = null) => + new AvaloniaX11Platform().Initialize(options ?? new X11PlatformOptions()); } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 14a9bc7c1a..5e0d8e4682 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -12,6 +12,7 @@ using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; +using Avalonia.X11.Glx; using static Avalonia.X11.XLib; // ReSharper disable IdentifierTypo // ReSharper disable StringLiteralTypo @@ -45,7 +46,7 @@ namespace Avalonia.X11 } private readonly Queue _inputQueue = new Queue(); private InputEventContainer _lastEvent; - + private bool _useRenderWindow = false; public X11Window(AvaloniaX11Platform platform, bool popup) { _platform = platform; @@ -55,7 +56,7 @@ namespace Avalonia.X11 _keyboard = platform.KeyboardDevice; _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing, XNames.XNClientWindow, _handle, IntPtr.Zero); - + var glfeature = AvaloniaLocator.Current.GetService(); XSetWindowAttributes attr = new XSetWindowAttributes(); var valueMask = default(SetWindowValuemask); @@ -71,16 +72,44 @@ namespace Avalonia.X11 attr.override_redirect = true; valueMask |= SetWindowValuemask.OverrideRedirect; } + + XVisualInfo? visualInfo = null; + + var glx = glfeature as GlxGlPlatformFeature; + if (glx != null) + visualInfo = *glx.Display.VisualInfo; + else if (glfeature == null) + visualInfo = _x11.TransparentVisualInfo; + + var egl = glfeature as EglGlPlatformFeature; + if (egl != null) + _useRenderWindow = true; + + var visual = IntPtr.Zero; + var depth = 24; + if (visualInfo != null) + { + visual = visualInfo.Value.visual; + depth = (int)visualInfo.Value.depth; + attr.colormap = XCreateColormap(_x11.Display, _x11.RootWindow, visualInfo.Value.visual, 0); + valueMask |= SetWindowValuemask.ColorMap; + } + _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0, - 24, - (int)CreateWindowArgs.InputOutput, IntPtr.Zero, + depth, + (int)CreateWindowArgs.InputOutput, + visual, new UIntPtr((uint)valueMask), ref attr); - _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, 24, - (int)CreateWindowArgs.InputOutput, - IntPtr.Zero, - new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | - SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); + + if (_useRenderWindow) + _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, depth, + (int)CreateWindowArgs.InputOutput, + visual, + new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | + SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); + else + _renderHandle = _handle; Handle = new PlatformHandle(_handle, "XID"); _realSize = new PixelSize(300, 200); @@ -99,16 +128,22 @@ namespace Avalonia.X11 XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length); XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1); - - var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService(); + + var surfaces = new List { - new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, () => Scaling) + new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, + depth, () => Scaling) }; - if (feature != null) + + if (egl != null) surfaces.Insert(0, - new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, + new EglGlPlatformSurface((EglDisplay)egl.Display, egl.DeferredContext, new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); + if (glx != null) + surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext, + new SurfaceInfo(this, _x11.Display, _handle, _renderHandle))); + Surfaces = surfaces.ToArray(); UpdateMotifHints(); XFlush(_x11.Display); @@ -252,7 +287,8 @@ namespace Avalonia.X11 if (ev.type == XEventName.MapNotify) { _mapped = true; - XMapWindow(_x11.Display, _renderHandle); + if (_useRenderWindow) + XMapWindow(_x11.Display, _renderHandle); } else if (ev.type == XEventName.UnmapNotify) _mapped = false; @@ -356,7 +392,9 @@ namespace Avalonia.X11 Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); }, DispatcherPriority.Layout); - XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height); + if (_useRenderWindow) + XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, + ev.ConfigureEvent.height); } else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle) { @@ -619,7 +657,7 @@ namespace Avalonia.X11 Closed?.Invoke(); } - if (_renderHandle != IntPtr.Zero) + if (_useRenderWindow && _renderHandle != IntPtr.Zero) { XDestroyWindow(_x11.Display, _renderHandle); _renderHandle = IntPtr.Zero; @@ -687,7 +725,8 @@ namespace Avalonia.X11 var pixelSize = ToPixelSize(clientSize); UpdateSizeHints(pixelSize); XConfigureResizeWindow(_x11.Display, _handle, pixelSize); - XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize); + if (_useRenderWindow) + XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize); XFlush(_x11.Display); if (force || (_popup && needImmediatePopupResize)) diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 255e84355a..c6e68b1c8b 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -27,11 +27,12 @@ namespace Avalonia.Skia if (gl != null) { var display = gl.ImmediateContext.Display; + gl.ImmediateContext.MakeCurrent(); using (var iface = display.Type == GlDisplayType.OpenGL2 ? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc)) : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc))) { - gl.ImmediateContext.MakeCurrent(); + GrContext = GRContext.Create(GRBackend.OpenGL, iface); } }