From d52d3417aea2fb281c91ad3d2907f0fa7c2475a7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 6 Jul 2019 21:09:51 +0300 Subject: [PATCH 01/15] Backported eb07c4b - jitter fix --- .../ControlCatalog.NetCore.csproj | 1 + samples/ControlCatalog.NetCore/Program.cs | 7 ++++ src/Avalonia.OpenGL/EglDisplay.cs | 1 + src/Avalonia.OpenGL/EglGlPlatformSurface.cs | 33 +++++++++++++++---- src/Avalonia.OpenGL/EglInterface.cs | 12 +++++++ .../IGlPlatformSurfaceRenderTarget.cs | 7 +++- .../Platform/IRenderTarget.cs | 5 +++ .../Rendering/DeferredRenderer.cs | 5 +++ .../Rendering/ManagedDeferredRendererLock.cs | 14 ++++++++ .../Rendering/UiThreadRenderTimer.cs | 32 ++++++++++++++++++ src/Skia/Avalonia.Skia/GlRenderTarget.cs | 2 ++ src/Windows/Avalonia.Win32/WindowImpl.cs | 28 +++++++++++----- 12 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/UiThreadRenderTimer.cs diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 589f41c06b..a25cbfc696 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -10,6 +10,7 @@ + diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index d13a5b5ef3..a364d191db 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using Avalonia; using Avalonia.Skia; +using Avalonia.ReactiveUI; namespace ControlCatalog.NetCore { @@ -45,6 +46,12 @@ namespace ControlCatalog.NetCore public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() + .With(new X11PlatformOptions {EnableMultiTouch = true}) + .With(new Win32PlatformOptions + { + EnableMultitouch = true, + AllowEglInitialization = true + }) .UseSkia() .UseReactiveUI() .UseDataGrid(); diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index b14932acfe..ec445a4605 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -97,6 +97,7 @@ namespace Avalonia.OpenGL public GlDisplayType Type { get; } public GlInterface GlInterface { get; } + public EglInterface EglInterface => _egl; public IGlContext CreateContext(IGlContext share) { var shareCtx = (EglContext)share; diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs index f5dd413b0f..d2e4543af3 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs @@ -26,31 +26,44 @@ namespace Avalonia.OpenGL public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() { var glSurface = _display.CreateWindowSurface(_info.Handle); - return new RenderTarget(_context, glSurface, _info); + return new RenderTarget(_display, _context, glSurface, _info); } - class RenderTarget : IGlPlatformSurfaceRenderTarget + class RenderTarget : IGlPlatformSurfaceRenderTargetWithCorruptionInfo { + private readonly EglDisplay _display; private readonly EglContext _context; private readonly EglSurface _glSurface; private readonly IEglWindowGlPlatformSurfaceInfo _info; + private PixelSize _initialSize; - public RenderTarget(EglContext context, EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) + public RenderTarget(EglDisplay display, EglContext context, + EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) { + _display = display; _context = context; _glSurface = glSurface; _info = info; + _initialSize = info.Size; } public void Dispose() => _glSurface.Dispose(); + public bool IsCorrupted => _initialSize != _info.Size; + public IGlPlatformSurfaceRenderingSession BeginDraw() { var l = _context.Lock(); try { + if (IsCorrupted) + throw new RenderTargetCorruptedException(); _context.MakeCurrent(_glSurface); - return new Session(_context, _glSurface, _info, l); + _display.EglInterface.WaitClient(); + _display.EglInterface.WaitGL(); + _display.EglInterface.WaitNative(); + + return new Session(_display, _context, _glSurface, _info, l); } catch { @@ -61,15 +74,19 @@ namespace Avalonia.OpenGL class Session : IGlPlatformSurfaceRenderingSession { - private readonly IGlContext _context; + private readonly EglContext _context; private readonly EglSurface _glSurface; private readonly IEglWindowGlPlatformSurfaceInfo _info; + private readonly EglDisplay _display; private IDisposable _lock; + - public Session(IGlContext context, EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info, + public Session(EglDisplay display, EglContext context, + EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info, IDisposable @lock) { _context = context; + _display = display; _glSurface = glSurface; _info = info; _lock = @lock; @@ -78,7 +95,11 @@ namespace Avalonia.OpenGL public void Dispose() { _context.Display.GlInterface.Flush(); + _display.EglInterface.WaitGL(); _glSurface.SwapBuffers(); + _display.EglInterface.WaitClient(); + _display.EglInterface.WaitGL(); + _display.EglInterface.WaitNative(); _context.Display.ClearContext(); _lock.Dispose(); } diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs index 00fcd97af0..2838e41461 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -91,6 +91,18 @@ namespace Avalonia.OpenGL [GlEntryPoint("eglGetConfigAttrib")] public EglGetConfigAttrib GetConfigAttrib { get; } + public delegate bool EglWaitGL(); + [GlEntryPoint("eglWaitGL")] + public EglWaitGL WaitGL { get; } + + public delegate bool EglWaitClient(); + [GlEntryPoint("eglWaitClient")] + public EglWaitGL WaitClient { get; } + + public delegate bool EglWaitNative(); + [GlEntryPoint("eglWaitNative")] + public EglWaitGL WaitNative { get; } + // ReSharper restore UnassignedGetOnlyAutoProperty } } diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs index 53da93315c..d198d46e5c 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs +++ b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs @@ -6,4 +6,9 @@ namespace Avalonia.OpenGL { IGlPlatformSurfaceRenderingSession BeginDraw(); } -} \ No newline at end of file + + public interface IGlPlatformSurfaceRenderTargetWithCorruptionInfo : IGlPlatformSurfaceRenderTarget + { + bool IsCorrupted { get; } + } +} diff --git a/src/Avalonia.Visuals/Platform/IRenderTarget.cs b/src/Avalonia.Visuals/Platform/IRenderTarget.cs index 522de64ec7..516bea782e 100644 --- a/src/Avalonia.Visuals/Platform/IRenderTarget.cs +++ b/src/Avalonia.Visuals/Platform/IRenderTarget.cs @@ -23,4 +23,9 @@ namespace Avalonia.Platform /// IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer); } + + public interface IRenderTargetWithCorruptionInfo : IRenderTarget + { + bool IsCorrupted { get; } + } } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 5293e1b978..6f2ffe916d 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -248,6 +248,11 @@ namespace Avalonia.Rendering { if (context != null) return context; + if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) + { + RenderTarget.Dispose(); + RenderTarget = null; + } if (RenderTarget == null) RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); return context = RenderTarget.CreateDrawingContext(this); diff --git a/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs b/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs index 75d8f036d6..2d4a39e026 100644 --- a/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs +++ b/src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs @@ -7,11 +7,25 @@ namespace Avalonia.Rendering public class ManagedDeferredRendererLock : IDeferredRendererLock { private readonly object _lock = new object(); + + /// + /// Tries to lock the target surface or window + /// + /// IDisposable if succeeded to obtain the lock public IDisposable TryLock() { if (Monitor.TryEnter(_lock)) return Disposable.Create(() => Monitor.Exit(_lock)); return null; } + + /// + /// Enters a waiting lock, only use from platform code, not from the renderer + /// + public IDisposable Lock() + { + Monitor.Enter(_lock); + return Disposable.Create(() => Monitor.Exit(_lock)); + } } } diff --git a/src/Avalonia.Visuals/Rendering/UiThreadRenderTimer.cs b/src/Avalonia.Visuals/Rendering/UiThreadRenderTimer.cs new file mode 100644 index 0000000000..dd6cf7ad15 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/UiThreadRenderTimer.cs @@ -0,0 +1,32 @@ +using System; +using System.Diagnostics; +using System.Reactive.Disposables; +using Avalonia.Threading; + +namespace Avalonia.Rendering +{ + /// + /// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms + /// + + public class UiThreadRenderTimer : DefaultRenderTimer + { + public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond) + { + } + + protected override IDisposable StartCore(Action tick) + { + bool cancelled = false; + var st = Stopwatch.StartNew(); + DispatcherTimer.Run(() => + { + if (cancelled) + return false; + tick(st.Elapsed); + return !cancelled; + }, TimeSpan.FromSeconds(1.0 / FramesPerSecond), DispatcherPriority.Render); + return Disposable.Create(() => cancelled = true); + } + } +} diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index cd8c334b53..e9e1727dfe 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -21,6 +21,8 @@ namespace Avalonia.Skia public void Dispose() => _surface.Dispose(); + public bool IsCorrupted => (_surface as IGlPlatformSurfaceRenderTargetWithCorruptionInfo)?.IsCorrupted == true; + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { var session = _surface.BeginDraw(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 081a713e95..11d6c467de 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -31,6 +31,7 @@ namespace Avalonia.Win32 private string _className; private IntPtr _hwnd; private IInputRoot _owner; + private ManagedDeferredRendererLock _rendererLock = new ManagedDeferredRendererLock(); private bool _trackingMouse; private bool _decorated = true; private bool _resizable = true; @@ -148,7 +149,9 @@ namespace Avalonia.Win32 if (customRendererFactory != null) return customRendererFactory.Create(root, loop); - return Win32Platform.UseDeferredRendering ? (IRenderer)new DeferredRenderer(root, loop) : new ImmediateRenderer(root); + return Win32Platform.UseDeferredRendering ? + (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock) : + new ImmediateRenderer(root); } public void Resize(Size value) @@ -627,18 +630,26 @@ namespace Avalonia.Win32 break; case UnmanagedMethods.WindowsMessage.WM_PAINT: - UnmanagedMethods.PAINTSTRUCT ps; - if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) + using (_rendererLock.Lock()) { - var f = Scaling; - var r = ps.rcPaint; - Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, (r.bottom - r.top) / f)); - UnmanagedMethods.EndPaint(_hwnd, ref ps); + UnmanagedMethods.PAINTSTRUCT ps; + if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) + { + var f = Scaling; + var r = ps.rcPaint; + Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, + (r.bottom - r.top) / f)); + UnmanagedMethods.EndPaint(_hwnd, ref ps); + } } return IntPtr.Zero; case UnmanagedMethods.WindowsMessage.WM_SIZE: + using (_rendererLock.Lock()) + { + // Do nothing here, just block until the pending frame render is completed on the render thread + } var size = (UnmanagedMethods.SizeCommand)wParam; if (Resized != null && @@ -704,7 +715,8 @@ namespace Avalonia.Win32 } } - return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); + using (_rendererLock.Lock()) + return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } static InputModifiers GetMouseModifiers(IntPtr wParam) From ce84902ec65f1793f7a811d1eefd941a3579fa9c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 4 Jun 2019 13:17:23 +0300 Subject: [PATCH 02/15] Use Direct3D9 as ANGLE platform type by default --- src/Avalonia.OpenGL/AngleOptions.cs | 18 ++++++++ src/Avalonia.OpenGL/EglDisplay.cs | 70 ++++++++++++++++++++--------- src/Avalonia.OpenGL/EglInterface.cs | 13 ++++++ 3 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 src/Avalonia.OpenGL/AngleOptions.cs diff --git a/src/Avalonia.OpenGL/AngleOptions.cs b/src/Avalonia.OpenGL/AngleOptions.cs new file mode 100644 index 0000000000..4b9c04f4e6 --- /dev/null +++ b/src/Avalonia.OpenGL/AngleOptions.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Avalonia.OpenGL +{ + public class AngleOptions + { + public enum PlatformApi + { + DirectX9, + DirectX11 + } + + public List AllowedPlatformApis = new List + { + PlatformApi.DirectX9 + }; + } +} diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/EglDisplay.cs index ec445a4605..b2b5a1a646 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/EglDisplay.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using Avalonia.Platform.Interop; using static Avalonia.OpenGL.EglConsts; @@ -13,21 +14,42 @@ namespace Avalonia.OpenGL private readonly int[] _contextAttributes; public IntPtr Handle => _display; + private AngleOptions.PlatformApi? _angleApi; public EglDisplay(EglInterface egl) { _egl = egl; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _egl.GetPlatformDisplayEXT != null) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - foreach (var dapi in new[] {EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE}) + if (_egl.GetPlatformDisplayEXT == null) + throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); + + var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis + ?? new List {AngleOptions.PlatformApi.DirectX9}; + + foreach (var platformApi in allowedApis) { + int dapi; + if (platformApi == AngleOptions.PlatformApi.DirectX9) + dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE; + else if (platformApi == AngleOptions.PlatformApi.DirectX11) + dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE; + else + continue; + _display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE }); - if(_display != IntPtr.Zero) + if (_display != IntPtr.Zero) + { + _angleApi = platformApi; break; + } } + + if (_display == IntPtr.Zero) + throw new OpenGlException("Unable to create ANGLE display"); } if (_display == IntPtr.Zero) @@ -64,29 +86,35 @@ namespace Avalonia.OpenGL if (!_egl.BindApi(cfg.Api)) continue; - var attribs = new[] + foreach(var stencilSize in new[]{8, 1, 0}) + foreach (var depthSize in new []{8, 1, 0}) { - EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_STENCIL_SIZE, 8, - EGL_DEPTH_SIZE, 8, - EGL_NONE - }; - if (!_egl.ChooseConfig(_display, attribs, out _config, 1, out int numConfigs)) - continue; - if (numConfigs == 0) - continue; - _contextAttributes = cfg.Attributes; - Type = cfg.Type; + var attribs = new[] + { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + + EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit, + + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_STENCIL_SIZE, stencilSize, + EGL_DEPTH_SIZE, depthSize, + EGL_NONE + }; + if (!_egl.ChooseConfig(_display, attribs, out _config, 1, out int numConfigs)) + continue; + if (numConfigs == 0) + continue; + _contextAttributes = cfg.Attributes; + Type = cfg.Type; + } } if (_contextAttributes == null) throw new OpenGlException("No suitable EGL config was found"); - + GlInterface = GlInterface.FromNativeUtf8GetProcAddress(b => _egl.GetProcAddress(b)); } diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs index 2838e41461..0e4b012dbe 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -102,6 +102,19 @@ namespace Avalonia.OpenGL public delegate bool EglWaitNative(); [GlEntryPoint("eglWaitNative")] public EglWaitGL WaitNative { get; } + + public delegate IntPtr EglQueryString(IntPtr display, int i); + + [GlEntryPoint("eglQueryString")] + public EglQueryString QueryStringNative { get; } + + public string QueryString(IntPtr display, int i) + { + var rv = QueryStringNative(display, i); + if (rv == IntPtr.Zero) + return null; + return Marshal.PtrToStringAnsi(rv); + } // ReSharper restore UnassignedGetOnlyAutoProperty } From 9b606ab513f4c8eb941c5d108c7ad76a96079f8d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 28 Jun 2019 15:21:05 +0300 Subject: [PATCH 03/15] [X11] Fixed NumLock --- src/Avalonia.X11/X11KeyTransform.cs | 10 +++------- src/Avalonia.X11/X11Window.cs | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.X11/X11KeyTransform.cs b/src/Avalonia.X11/X11KeyTransform.cs index 26495111d1..c68cb04733 100644 --- a/src/Avalonia.X11/X11KeyTransform.cs +++ b/src/Avalonia.X11/X11KeyTransform.cs @@ -221,12 +221,8 @@ namespace Avalonia.X11 //{ X11Key.?, Key.DeadCharProcessed } }; - public static Key ConvertKey(IntPtr key) - { - var ikey = key.ToInt32(); - Key result; - return KeyDic.TryGetValue((X11Key)ikey, out result) ? result : Key.None; - } -} + public static Key ConvertKey(X11Key key) + => KeyDic.TryGetValue(key, out var result) ? result : Key.None; + } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index c5e77fe352..878ed12b66 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -419,10 +419,21 @@ namespace Avalonia.X11 return; var buffer = stackalloc byte[40]; - var latinKeysym = XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, 0); + var index = ev.KeyEvent.state.HasFlag(XModifierMask.ShiftMask); + + // We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway + var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32(); + + // Manually switch the Shift index for the keypad, + // there should be a proper way to do this + if (ev.KeyEvent.state.HasFlag(XModifierMask.Mod2Mask) + && key > X11Key.Num_Lock && key <= X11Key.KP_9) + key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32(); + + ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, - X11KeyTransform.ConvertKey(latinKeysym), TranslateModifiers(ev.KeyEvent.state)), ref ev); + X11KeyTransform.ConvertKey(key), TranslateModifiers(ev.KeyEvent.state)), ref ev); if (ev.type == XEventName.KeyPress) { From f65fcb6fa413199e02258f086eb098d532364e6d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 6 Jul 2019 21:25:45 +0300 Subject: [PATCH 04/15] [X11] Blacklist llvmpipe --- src/Avalonia.OpenGL/GlInterface.cs | 18 ++++++++++++++---- src/Avalonia.X11/Glx/GlxDisplay.cs | 13 +++++++++++++ src/Avalonia.X11/X11Platform.cs | 8 ++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 718afc4a94..f556949cfa 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -9,12 +9,14 @@ namespace Avalonia.OpenGL public class GlInterface : GlInterfaceBase { public string Version { get; } + public string Vendor { get; } + public string Renderer { get; } public GlInterface(Func getProcAddress) : base(getProcAddress) { - var versionPtr = GetString(GlConsts.GL_VERSION); - if (versionPtr != IntPtr.Zero) - Version = Marshal.PtrToStringAnsi(versionPtr); + Version = GetString(GlConsts.GL_VERSION); + Renderer = GetString(GlConsts.GL_RENDERER); + Vendor = GetString(GlConsts.GL_VENDOR); } public GlInterface(Func n) : this(ConvertNative(n)) @@ -54,7 +56,15 @@ namespace Avalonia.OpenGL public delegate IntPtr GlGetString(int v); [GlEntryPoint("glGetString")] - public GlGetString GetString { get; } + public GlGetString GetStringNative { get; } + + public string GetString(int v) + { + var ptr = GetStringNative(v); + if (ptr != IntPtr.Zero) + return Marshal.PtrToStringAnsi(ptr); + return null; + } public delegate void GlGetIntegerv(int name, out int rv); [GlEntryPoint("glGetIntegerv")] diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index 5602b33280..04f2a7137c 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -90,6 +90,19 @@ namespace Avalonia.X11.Glx GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress); if (GlInterface.Version == null) throw new OpenGlException("GL version string is null, aborting"); + if (GlInterface.Renderer == null) + throw new OpenGlException("GL renderer string is null, aborting"); + + if (Environment.GetEnvironmentVariable("AVALONIA_GLX_IGNORE_RENDERER_BLACKLIST") != "1") + { + var blacklist = AvaloniaLocator.Current.GetService() + ?.GlxRendererBlacklist; + if (blacklist != null) + foreach(var item in blacklist) + if (GlInterface.Renderer.Contains(item)) + throw new OpenGlException($"Renderer '{GlInterface.Renderer}' is blacklisted by '{item}'"); + } + } public void ClearContext() => Glx.MakeContextCurrent(_x11.Display, diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index ce03113169..47d5276fbc 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -95,6 +95,14 @@ namespace Avalonia { public bool UseEGL { get; set; } public bool UseGpu { get; set; } = true; + + public List GlxRendererBlacklist { get; set; } = new List + { + // llvmpipe is a software GL rasterizer. If it's returned by glGetString, + // that usually means that something in the system is horribly misconfigured + // and sometimes attempts to use GLX might cause a segfault + "llvmpipe" + }; public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication"; } public static class AvaloniaX11PlatformExtensions From f3e504ca152922ea1492e28a0be12949c1729316 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 6 Jul 2019 21:33:03 +0300 Subject: [PATCH 05/15] Bump version for 0.8.1 --- build/SharedVersion.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 7ea1dd0c65..16a4b828f4 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> Avalonia - 0.8.0 + 0.8.1 Copyright 2018 © The AvaloniaUI Project https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md https://github.com/AvaloniaUI/Avalonia/ From bc2cc9928c71d7be9fa8e143002a3c5ea164eff0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 6 Jul 2019 21:34:58 +0300 Subject: [PATCH 06/15] Compilation --- samples/ControlCatalog.NetCore/Program.cs | 4 ---- src/Avalonia.Native/Avalonia.Native.csproj | 2 -- src/Avalonia.OpenGL/EglInterface.cs | 1 + 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index a364d191db..c58d071ac8 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -3,8 +3,6 @@ using System.Diagnostics; using System.Linq; using System.Threading; using Avalonia; -using Avalonia.Skia; -using Avalonia.ReactiveUI; namespace ControlCatalog.NetCore { @@ -46,10 +44,8 @@ namespace ControlCatalog.NetCore public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .With(new X11PlatformOptions {EnableMultiTouch = true}) .With(new Win32PlatformOptions { - EnableMultitouch = true, AllowEglInitialization = true }) .UseSkia() diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index c8ee73ad5d..9d6a82a2fb 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -7,8 +7,6 @@ /usr/bin/castxml /usr/local/bin/castxml true - - $(MSBuildThisFileDirectory)/Generated diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/EglInterface.cs index 0e4b012dbe..fd1374f3fe 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using Avalonia.Platform; using Avalonia.Platform.Interop; From 80a3d0471a52f0fe1de4ddc92d8f2c159bc432c2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 6 Jul 2019 22:00:08 +0300 Subject: [PATCH 07/15] Azure pipelines update --- azure-pipelines.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 39333f37ba..981b17b0de 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,7 +32,7 @@ jobs: - job: macOS pool: - vmImage: 'xcode9-macos10.13' + vmImage: 'macOS-10.14' steps: - task: DotNetCoreInstaller@0 inputs: @@ -49,7 +49,7 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx10.13' + sdk: 'macosx10.14' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcodeVersion: 'default' # Options: 8, 9, default, specifyPath @@ -134,3 +134,4 @@ jobs: pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip' artifactName: 'Samples' condition: succeeded() + From 94e2cbbd43a2bb246b0a68c726bd68d8d627cba7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 6 Jul 2019 22:02:46 +0300 Subject: [PATCH 08/15] Updated Build.cs to version from master --- nukebuild/Build.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index bb31034299..84092d52eb 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -122,6 +122,14 @@ partial class Build : NukeBuild foreach(var fw in frameworks) { + if (fw.StartsWith("net4") + && RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + && Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1") + { + Information($"Skipping {fw} tests on Linux - https://github.com/mono/mono/issues/13969"); + continue; + } + Information("Running for " + fw); DotNetTest(c => { From 69912114ee23c183fbd688d045d5f9e6f591153f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Aug 2019 20:46:25 +0100 Subject: [PATCH 09/15] fix osx clipboard string encoding. --- native/Avalonia.Native/src/OSX/AvnString.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index b491cf2a92..9fb6101438 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -12,6 +12,7 @@ class AvnStringImpl : public virtual ComSingleObject Date: Thu, 8 Aug 2019 20:54:51 +0100 Subject: [PATCH 10/15] bump version --- build/SharedVersion.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 16a4b828f4..2ef5c8079d 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> Avalonia - 0.8.1 + 0.8.2 Copyright 2018 © The AvaloniaUI Project https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md https://github.com/AvaloniaUI/Avalonia/ From a1c458417b1abdc9dce86c18391c209eb85d90df Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 9 Aug 2019 10:52:37 +0100 Subject: [PATCH 11/15] make a copy of the clipboard string. --- native/Avalonia.Native/src/OSX/AvnString.mm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 9fb6101438..7b6f4c0a3e 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -11,7 +11,7 @@ class AvnStringImpl : public virtual ComSingleObject { private: - NSString* _string; + int _length; const char* _cstring; public: @@ -19,8 +19,11 @@ public: AvnStringImpl(NSString* string) { - _string = string; - _cstring = [_string cStringUsingEncoding:NSUTF8StringEncoding]; + auto cstring = [string cStringUsingEncoding:NSUTF8StringEncoding]; + _length = (int)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + _cstring = (const char*)malloc(_length); + memcpy((void*)_cstring, (void*)cstring, _length); } virtual HRESULT Pointer(void**retOut) override @@ -45,7 +48,7 @@ public: return E_POINTER; } - *retOut = (int)[_string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + *retOut = _length; return S_OK; } From 8422543ac90bdaece011b229f4d28d15090fc6ab Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 9 Aug 2019 11:51:45 +0100 Subject: [PATCH 12/15] free string in dtor --- native/Avalonia.Native/src/OSX/AvnString.mm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 7b6f4c0a3e..b62fe8a968 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -18,14 +18,21 @@ public: FORWARD_IUNKNOWN() AvnStringImpl(NSString* string) - { + { auto cstring = [string cStringUsingEncoding:NSUTF8StringEncoding]; _length = (int)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - _cstring = (const char*)malloc(_length); + _cstring = (const char*)malloc(_length + 5); + + memset((void*)_cstring, 0, _length + 5); memcpy((void*)_cstring, (void*)cstring, _length); } + virtual ~AvnStringImpl() + { + free((void*)_cstring); + } + virtual HRESULT Pointer(void**retOut) override { @autoreleasepool From a79eb78b39c7518e02d3eaa95d7652b0fb0d3df8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 9 Aug 2019 11:52:25 +0100 Subject: [PATCH 13/15] clear clipboard with empty string on startup --- native/Avalonia.Native/src/OSX/clipboard.mm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index 53c1fe3c2c..33985211f6 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -8,6 +8,14 @@ class Clipboard : public ComSingleObject { public: FORWARD_IUNKNOWN() + + Clipboard() + { + NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; + [pasteBoard clearContents]; + [pasteBoard setString:@"" forType:NSPasteboardTypeString]; + } + virtual HRESULT GetText (IAvnString**ppv) override { @autoreleasepool From 4910c17d7dd1ad6fbf4d5fad32f1c44ba56b1c43 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 9 Aug 2019 11:54:18 +0100 Subject: [PATCH 14/15] safely clear pasteboard osx --- native/Avalonia.Native/src/OSX/clipboard.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index 33985211f6..503f93e8fa 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -47,7 +47,9 @@ public: { @autoreleasepool { - [[NSPasteboard generalPasteboard] clearContents]; + NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; + [pasteBoard clearContents]; + [pasteBoard setString:@"" forType:NSPasteboardTypeString]; } return S_OK; From a4eed991f4b319c92e6a827355b614af8347505f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 9 Aug 2019 12:26:04 +0100 Subject: [PATCH 15/15] dont clear clipboard but do a dummy read. --- native/Avalonia.Native/src/OSX/clipboard.mm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index 503f93e8fa..6e4d3ce668 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -12,8 +12,7 @@ public: Clipboard() { NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; - [pasteBoard clearContents]; - [pasteBoard setString:@"" forType:NSPasteboardTypeString]; + [pasteBoard stringForType:NSPasteboardTypeString]; } virtual HRESULT GetText (IAvnString**ppv) override