From c112ce30c580db8fd03226c5adcb016766a981d5 Mon Sep 17 00:00:00 2001 From: Kermalis <29823718+Kermalis@users.noreply.github.com> Date: Fri, 18 Jan 2019 21:19:46 -0500 Subject: [PATCH 01/11] Update NumericUpDown Text when losing focus --- src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 1f8e991d2e..18cef7d64e 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -287,6 +287,13 @@ namespace Avalonia.Controls ValueProperty.Changed.Subscribe(OnValueChanged); } + /// + protected override void OnLostFocus(RoutedEventArgs e) + { + CommitInput(); + base.OnLostFocus(e); + } + /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { From 6badd34c6a4ee9548b5fc6f86f141da62581c853 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Jan 2019 18:53:47 +0300 Subject: [PATCH 02/11] [X11] Prevent possible blocking calls to XNextEvent --- src/Avalonia.X11/X11PlatformThreading.cs | 42 ++++++++++-------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs index f66afc4a83..0215e43d98 100644 --- a/src/Avalonia.X11/X11PlatformThreading.cs +++ b/src/Avalonia.X11/X11PlatformThreading.cs @@ -164,39 +164,33 @@ namespace Avalonia.X11 void HandleX11(CancellationToken cancellationToken) { - while (true) + while (XPending(_display) != 0) { - var pending = XPending(_display); - if (pending == 0) - break; - while (pending > 0) + if (cancellationToken.IsCancellationRequested) + return; + XNextEvent(_display, out var xev); + if (xev.type == XEventName.GenericEvent) + XGetEventData(_display, &xev.GenericEventCookie); + try { - if (cancellationToken.IsCancellationRequested) - return; - XNextEvent(_display, out var xev); if (xev.type == XEventName.GenericEvent) - XGetEventData(_display, &xev.GenericEventCookie); - pending--; - try { - if (xev.type == XEventName.GenericEvent) + if (_platform.XI2 != null && _platform.Info.XInputOpcode == + xev.GenericEventCookie.extension) { - if (_platform.XI2 != null && _platform.Info.XInputOpcode == - xev.GenericEventCookie.extension) - { - _platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data); - } + _platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data); } - else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler)) - handler(xev); - } - finally - { - if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null) - XFreeEventData(_display, &xev.GenericEventCookie); } + else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler)) + handler(xev); + } + finally + { + if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null) + XFreeEventData(_display, &xev.GenericEventCookie); } } + Dispatcher.UIThread.RunJobs(); } From ed9dff6b8f4006c6da2f056019c7bc60117b76fd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Jan 2019 18:55:50 +0300 Subject: [PATCH 03/11] [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); } } From 9eef03707993612bd8352ddba589005b10143443 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 21 Jan 2019 09:32:02 +0300 Subject: [PATCH 04/11] [X11] Don't use XSync with discard=true from the main display connection --- src/Avalonia.X11/X11Window.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 5e0d8e4682..136fbc4ff5 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -171,8 +171,6 @@ namespace Avalonia.X11 XLockDisplay(_display); XGetGeometry(_display, _parent, out var geo); XResizeWindow(_display, Handle, geo.width, geo.height); - XFlush(_display); - XSync(_display, true); XUnlockDisplay(_display); return new PixelSize(geo.width, geo.height); } From f1e3fb8d22d9f0d0816d57193314aafb07b7f12e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 21 Jan 2019 09:33:27 +0300 Subject: [PATCH 05/11] [X11] Always use a separate child window for rendering if OpenGL is available --- src/Avalonia.X11/X11Window.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 136fbc4ff5..898b80550a 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -74,7 +74,9 @@ namespace Avalonia.X11 } XVisualInfo? visualInfo = null; - + + // OpenGL seems to be do weird things to it's current window which breaks resize sometimes + _useRenderWindow = glfeature != null; var glx = glfeature as GlxGlPlatformFeature; if (glx != null) @@ -83,8 +85,6 @@ namespace Avalonia.X11 visualInfo = _x11.TransparentVisualInfo; var egl = glfeature as EglGlPlatformFeature; - if (egl != null) - _useRenderWindow = true; var visual = IntPtr.Zero; var depth = 24; From abd05f0ed76cc1148ab3899142dee5cb61314e6d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 23 Jan 2019 12:32:01 +0300 Subject: [PATCH 06/11] [X11] Abort GLX context creation if GL_VERSION is null --- src/Avalonia.X11/Glx/GlxDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index a94f2ba625..7ca2136709 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -80,6 +80,8 @@ namespace Avalonia.X11.Glx ImmediateContext.MakeCurrent(); GlInterface = GlInterface.FromNativeUtf8GetProcAddress(p => GlxGetProcAddress(p)); + if (GlInterface.Version == null) + throw new OpenGlException("GL version string is null, aborting"); } public void ClearContext() => GlxMakeCurrent(_x11.Display, IntPtr.Zero, IntPtr.Zero); From a7b09c05d58e0f55dd0a98cc8ac47f5c4437b69c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 23 Jan 2019 12:59:04 +0300 Subject: [PATCH 07/11] [GLX] Try newer versions of OpenGL first --- src/Avalonia.X11/Glx/Glx.cs | 5 ++++- src/Avalonia.X11/Glx/GlxConsts.cs | 9 ++++++++ src/Avalonia.X11/Glx/GlxDisplay.cs | 34 +++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.X11/Glx/Glx.cs b/src/Avalonia.X11/Glx/Glx.cs index 2382d66a84..de558e8e6a 100644 --- a/src/Avalonia.X11/Glx/Glx.cs +++ b/src/Avalonia.X11/Glx/Glx.cs @@ -18,7 +18,10 @@ namespace Avalonia.X11.Glx [DllImport(libGL, EntryPoint = "glXCreateContext")] public static extern IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct); - + + [DllImport(libGL, EntryPoint = "glXCreateContextAttribsARB")] + public static extern IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList, + bool direct, int[] attribs); [DllImport(libGL, EntryPoint = "glXGetProcAddress")] public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer); diff --git a/src/Avalonia.X11/Glx/GlxConsts.cs b/src/Avalonia.X11/Glx/GlxConsts.cs index 71f6636371..65f253696a 100644 --- a/src/Avalonia.X11/Glx/GlxConsts.cs +++ b/src/Avalonia.X11/Glx/GlxConsts.cs @@ -93,5 +93,14 @@ namespace Avalonia.X11.Glx public const int GLX_SAMPLES = 0x186a1 /*100001*/; public const int GLX_PbufferClobber = 0; public const int GLX_BufferSwapComplete = 1; + public const int GLX_CONTEXT_DEBUG_BIT_ARB = 0x00000001; + public const int GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x00000002; + public const int GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; + public const int GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; + public const int GLX_CONTEXT_FLAGS_ARB = 0x2094; + public const int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; + public const int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; + public const int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126; + } } diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index 7ca2136709..7e6d2328e5 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -89,10 +89,38 @@ namespace Avalonia.X11.Glx public GlxContext CreateContext(IGlContext share) { var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero; - var h = GlxCreateContext(_x11.Display, _visual, sharelist, true); - if (h == IntPtr.Zero) + IntPtr handle = default; + foreach (var ver in new[] + { + new Version(4, 0), new Version(3, 2), + new Version(3, 0), new Version(2, 0) + }) + { + + var attrs = new[] + { + GLX_CONTEXT_MAJOR_VERSION_ARB, ver.Major, + GLX_CONTEXT_MINOR_VERSION_ARB, ver.Minor, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + 0 + }; + try + { + handle = GlxCreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs); + if (handle != IntPtr.Zero) + break; + } + catch + { + break; + } + } + + if(handle == IntPtr.Zero) + handle = GlxCreateContext(_x11.Display, _visual, sharelist, true); + if (handle == IntPtr.Zero) throw new OpenGlException("Unable to create direct GLX context"); - return new GlxContext(h, this, _x11); + return new GlxContext(handle, this, _x11); } public void SwapBuffers(IntPtr xid) => GlxSwapBuffers(_x11.Display, xid); From aa18bbf22475f7731f322c3f7befe27f3cddfe85 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 23 Jan 2019 17:06:36 +0300 Subject: [PATCH 08/11] [GLX] Use glxGetProcAddress and always pass at least something to GlxMakeContextCurrent --- src/Avalonia.OpenGL/GlEntryPointAttribute.cs | 1 + src/Avalonia.OpenGL/GlInterface.cs | 21 ++--- src/Avalonia.OpenGL/GlInterfaceBase.cs | 25 ++++++ src/Avalonia.X11/Glx/Glx.cs | 85 ++++++++++++++------ src/Avalonia.X11/Glx/GlxContext.cs | 17 ++-- src/Avalonia.X11/Glx/GlxDisplay.cs | 46 ++++++----- src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs | 4 +- 7 files changed, 133 insertions(+), 66 deletions(-) diff --git a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs index fbde8e57be..016c3d0af9 100644 --- a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs +++ b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs @@ -2,6 +2,7 @@ using System; namespace Avalonia.OpenGL { + [AttributeUsage(AttributeTargets.Property)] public class GlEntryPointAttribute : Attribute { public string EntryPoint { get; } diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 7921989c04..718afc4a94 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -8,31 +8,24 @@ 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 GlInterface(Func n) : this(ConvertNative(n)) + { + + } - public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true); + public static GlInterface FromNativeUtf8GetProcAddress(Func getProcAddress) => + new GlInterface(getProcAddress); + public T GetProcAddress(string proc) => Marshal.GetDelegateForFunctionPointer(GetProcAddress(proc)); // ReSharper disable UnassignedGetOnlyAutoProperty diff --git a/src/Avalonia.OpenGL/GlInterfaceBase.cs b/src/Avalonia.OpenGL/GlInterfaceBase.cs index 8b3eb2797d..89ec0e77d4 100644 --- a/src/Avalonia.OpenGL/GlInterfaceBase.cs +++ b/src/Avalonia.OpenGL/GlInterfaceBase.cs @@ -1,13 +1,17 @@ using System; using System.Reflection; using System.Runtime.InteropServices; +using Avalonia.Platform.Interop; namespace Avalonia.OpenGL { public class GlInterfaceBase { + + private readonly Func _getProcAddress; public GlInterfaceBase(Func getProcAddress) { + _getProcAddress = getProcAddress; foreach (var prop in this.GetType().GetProperties()) { var a = prop.GetCustomAttribute(); @@ -24,5 +28,26 @@ namespace Avalonia.OpenGL } } } + + protected static Func ConvertNative(Func func) => + (proc, optional) => + { + using (var u = new Utf8Buffer(proc)) + { + var rv = func(u); + if (rv == IntPtr.Zero && !optional) + throw new OpenGlException("Missing function " + proc); + return rv; + } + }; + + public GlInterfaceBase(Func nativeGetProcAddress) : this(ConvertNative(nativeGetProcAddress)) + { + + } + + public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true); + public IntPtr GetProcAddress(string proc, bool optional) => _getProcAddress(proc, optional); + } } diff --git a/src/Avalonia.X11/Glx/Glx.cs b/src/Avalonia.X11/Glx/Glx.cs index de558e8e6a..c3a2fd2050 100644 --- a/src/Avalonia.X11/Glx/Glx.cs +++ b/src/Avalonia.X11/Glx/Glx.cs @@ -2,57 +2,90 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using Avalonia.OpenGL; using Avalonia.Platform.Interop; +// ReSharper disable UnassignedGetOnlyAutoProperty namespace Avalonia.X11.Glx { - static unsafe class Glx + unsafe class GlxInterface : GlInterfaceBase { private const string libGL = "libGL.so.1"; - [DllImport(libGL, EntryPoint = "glXMakeCurrent")] - public static extern bool GlxMakeCurrent(IntPtr display, IntPtr drawable, IntPtr context); + [GlEntryPointAttribute("glXMakeContextCurrent")] + public GlxMakeContextCurrent MakeContextCurrent { get; } + public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context); + [GlEntryPoint("glXCreatePbuffer")] + public GlxCreatePbuffer CreatePbuffer { get; } + + public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list); + + [GlEntryPointAttribute("glXChooseVisual")] + public GlxChooseVisual ChooseVisual { get; } + public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList); - [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); + [GlEntryPointAttribute("glXCreateContext")] + public GlxCreateContext CreateContext { get; } + public delegate IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct); + - [DllImport(libGL, EntryPoint = "glXCreateContextAttribsARB")] - public static extern IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList, + [GlEntryPointAttribute("glXCreateContextAttribsARB")] + public GlxCreateContextAttribsARB CreateContextAttribsARB { get; } + public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList, bool direct, int[] attribs); + [DllImport(libGL, EntryPoint = "glXGetProcAddress")] - public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer); + public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer); + + + [GlEntryPointAttribute("glXDestroyContext")] + public GlxDestroyContext DestroyContext { get; } + public delegate void GlxDestroyContext(IntPtr dpy, IntPtr ctx); - [DllImport(libGL, EntryPoint = "glXDestroyContext")] - public static extern void GlxDestroyContext(IntPtr dpy, IntPtr ctx); + [GlEntryPointAttribute("glXChooseFBConfig")] + public GlxChooseFBConfig ChooseFBConfig { get; } + public delegate IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements); - [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) + public 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); + return ChooseFBConfig(dpy, screen, arr, out nelements); } - [DllImport(libGL, EntryPoint = "glXGetVisualFromFBConfig")] - public static extern XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config); + + [GlEntryPointAttribute("glXGetVisualFromFBConfig")] + public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; } + public delegate XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config); - [DllImport(libGL, EntryPoint = "glXGetFBConfigAttrib")] - public static extern int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value); + [GlEntryPointAttribute("glXGetFBConfigAttrib")] + public GlxGetFBConfigAttrib GetFBConfigAttrib { get; } + public delegate int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value); - [DllImport(libGL, EntryPoint = "glXSwapBuffers")] - public static extern void GlxSwapBuffers(IntPtr dpy, IntPtr drawable); + [GlEntryPointAttribute("glXSwapBuffers")] + public GlxSwapBuffers SwapBuffers { get; } + public delegate void GlxSwapBuffers(IntPtr dpy, IntPtr drawable); - [DllImport(libGL, EntryPoint = "glXWaitX")] - public static extern void GlxWaitX(); - [DllImport(libGL, EntryPoint = "glXWaitGL")] - public static extern void GlxWaitGL(); + [GlEntryPointAttribute("glXWaitX")] + public GlxWaitX WaitX { get; } + public delegate void GlxWaitX(); + + + [GlEntryPointAttribute("glXWaitGL")] + public GlxWaitGL WaitGL { get; } + public delegate void GlxWaitGL(); + + public delegate int GlGetError(); + [GlEntryPoint("glGetError")] + public GlGetError GetError { get; } + + public GlxInterface() : base(GlxGetProcAddress) + { + } } } diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs index b263d77666..dd95841d57 100644 --- a/src/Avalonia.X11/Glx/GlxContext.cs +++ b/src/Avalonia.X11/Glx/GlxContext.cs @@ -2,19 +2,22 @@ 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; } + public GlxInterface Glx { get; } private readonly X11Info _x11; + private readonly IntPtr _defaultXid; private readonly object _lock = new object(); - public GlxContext(IntPtr handle, GlxDisplay display, X11Info x11) + public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid) { Handle = handle; + Glx = glx; _x11 = x11; + _defaultXid = defaultXid; Display = display; } @@ -27,8 +30,12 @@ namespace Avalonia.X11.Glx return Disposable.Create(() => Monitor.Exit(_lock)); } - public void MakeCurrent() => MakeCurrent(IntPtr.Zero); - - public void MakeCurrent(IntPtr xid) => GlxMakeCurrent(_x11.Display, xid, Handle); + public void MakeCurrent() => MakeCurrent(_defaultXid); + + public void MakeCurrent(IntPtr xid) + { + if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle)) + throw new OpenGlException("glXMakeContextCurrent failed "); + } } } diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index 7e6d2328e5..5602b33280 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -2,7 +2,7 @@ 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 @@ -19,7 +19,7 @@ namespace Avalonia.X11.Glx public GlxContext ImmediateContext { get; } public GlxContext DeferredContext { get; } - + public GlxInterface Glx { get; } = new GlxInterface(); public GlxDisplay(X11Info x11) { _x11 = x11; @@ -28,7 +28,7 @@ namespace Avalonia.X11.Glx { GLX_X_RENDERABLE, 1, GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT, GLX_DOUBLEBUFFER, 1, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, @@ -45,12 +45,12 @@ namespace Avalonia.X11.Glx baseAttribs, }) { - var ptr = GlxChooseFBConfig(_x11.Display, x11.DefaultScreen, + var ptr = Glx.ChooseFBConfig(_x11.Display, x11.DefaultScreen, attribs, out var count); for (var c = 0 ; c < count; c++) { - var visual = GlxGetVisualFromFBConfig(_x11.Display, ptr[c]); + var visual = Glx.GetVisualFromFBConfig(_x11.Display, ptr[c]); // We prefer 32 bit visuals if (_fbconfig == IntPtr.Zero || visual->depth == 32) { @@ -70,23 +70,33 @@ namespace Avalonia.X11.Glx if (_visual == null) throw new OpenGlException("Unable to get visual info from FBConfig"); - if (GlxGetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0) + if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0) SampleCount = samples; - if (GlxGetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0) + if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0) StencilSize = stencil; - ImmediateContext = CreateContext(null); - DeferredContext = CreateContext(ImmediateContext); + var pbuffers = Enumerable.Range(0, 2).Select(_ => Glx.CreatePbuffer(_x11.Display, _fbconfig, new[] + { + GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 + })).ToList(); + + XLib.XFlush(_x11.Display); + + ImmediateContext = CreateContext(pbuffers[0],null); + DeferredContext = CreateContext(pbuffers[1], ImmediateContext); ImmediateContext.MakeCurrent(); - - GlInterface = GlInterface.FromNativeUtf8GetProcAddress(p => GlxGetProcAddress(p)); + var err = Glx.GetError(); + + GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress); if (GlInterface.Version == null) throw new OpenGlException("GL version string is null, aborting"); } - - public void ClearContext() => GlxMakeCurrent(_x11.Display, IntPtr.Zero, IntPtr.Zero); - public GlxContext CreateContext(IGlContext share) + public void ClearContext() => Glx.MakeContextCurrent(_x11.Display, + IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + public GlxContext CreateContext(IGlContext share) => CreateContext(IntPtr.Zero, share); + public GlxContext CreateContext(IntPtr defaultXid, IGlContext share) { var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero; IntPtr handle = default; @@ -106,7 +116,7 @@ namespace Avalonia.X11.Glx }; try { - handle = GlxCreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs); + handle = Glx.CreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs); if (handle != IntPtr.Zero) break; } @@ -116,13 +126,11 @@ namespace Avalonia.X11.Glx } } - if(handle == IntPtr.Zero) - handle = GlxCreateContext(_x11.Display, _visual, sharelist, true); if (handle == IntPtr.Zero) throw new OpenGlException("Unable to create direct GLX context"); - return new GlxContext(handle, this, _x11); + return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid); } - public void SwapBuffers(IntPtr xid) => GlxSwapBuffers(_x11.Display, xid); + public void SwapBuffers(IntPtr xid) => Glx.SwapBuffers(_x11.Display, xid); } } diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs index f6a4090dc0..45a46bd6f5 100644 --- a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs +++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs @@ -70,9 +70,9 @@ namespace Avalonia.X11.Glx public void Dispose() { _context.Display.GlInterface.Flush(); - Glx.GlxWaitGL(); + _context.Glx.WaitGL(); _context.Display.SwapBuffers(_info.Handle); - Glx.GlxWaitX(); + _context.Glx.WaitX(); _context.Display.ClearContext(); _lock.Dispose(); } From 88fdbcf2a967d85d1a668f00de3ee85c6eee33cb Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 23 Jan 2019 17:07:00 +0300 Subject: [PATCH 09/11] [X11] Call XCreateIC *after* creating a window, not before --- src/Avalonia.X11/X11Window.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 898b80550a..cbff4e38cf 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -54,8 +54,7 @@ namespace Avalonia.X11 _x11 = platform.Info; _mouse = platform.MouseDevice; _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); @@ -146,6 +145,8 @@ namespace Avalonia.X11 Surfaces = surfaces.ToArray(); UpdateMotifHints(); + _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing, + XNames.XNClientWindow, _handle, IntPtr.Zero); XFlush(_x11.Display); } From f97a69990463c7d75997aa03763882013476b5fa Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 23 Jan 2019 17:42:54 +0300 Subject: [PATCH 10/11] Switch to custom-built skia --- build/SkiaSharp.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index a43c99e978..cf8e0fd13a 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,6 +1,6 @@  - + From 533284cd616a91c911951effc50f397f9fc0232d Mon Sep 17 00:00:00 2001 From: Artyom Date: Thu, 24 Jan 2019 15:30:05 +0300 Subject: [PATCH 11/11] Fix AvaloniaProperty registration type --- src/Avalonia.ReactiveUI/ReactiveUserControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index 04c6f100a6..43e2ef93b6 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -16,7 +16,7 @@ namespace Avalonia public class ReactiveUserControl : UserControl, IViewFor where TViewModel : class { public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty - .Register, TViewModel>(nameof(ViewModel)); + .Register, TViewModel>(nameof(ViewModel)); /// /// Initializes a new instance of the class. @@ -41,4 +41,4 @@ namespace Avalonia set => ViewModel = (TViewModel)value; } } -} \ No newline at end of file +}