diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 804ca1f9b8..6a40f7187d 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -11,6 +11,7 @@ + diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index c35b4a7919..4027c5cd63 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -47,7 +47,11 @@ namespace ControlCatalog.NetCore => AppBuilder.Configure() .UsePlatformDetect() .With(new X11PlatformOptions {EnableMultiTouch = true}) - .With(new Win32PlatformOptions {EnableMultitouch = true}) + .With(new Win32PlatformOptions + { + EnableMultitouch = true, + AllowEglInitialization = true + }) .UseSkia() .UseReactiveUI(); 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 b14932acfe..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)); } @@ -97,6 +125,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..0a99778ddf 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; @@ -15,13 +16,28 @@ namespace Avalonia.OpenGL { } + [DllImport("libegl.dll", CharSet = CharSet.Ansi)] + static extern IntPtr eglGetProcAddress(string proc); + static Func Load() { var os = AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem; if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android) return Load("libEGL.so.1"); if (os == OperatingSystemType.WinNT) - return Load(@"libegl.dll"); + { + var disp = eglGetProcAddress("eglGetPlatformDisplayEXT"); + if (disp == IntPtr.Zero) + throw new OpenGlException("libegl.dll doesn't have eglGetPlatformDisplayEXT entry point"); + return (name, optional) => + { + var r = eglGetProcAddress(name); + if (r == IntPtr.Zero && !optional) + throw new OpenGlException($"Entry point {r} is not found"); + return r; + }; + } + throw new PlatformNotSupportedException(); } @@ -91,6 +107,31 @@ 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; } + + 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 } } 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 c83a8436b4..0d077d2a3a 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -245,6 +245,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 7c0c42ca37..a7c1d0a38b 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -8,7 +8,7 @@ using static Avalonia.OpenGL.GlConsts; namespace Avalonia.Skia { - internal class GlRenderTarget : IRenderTarget + internal class GlRenderTarget : IRenderTargetWithCorruptionInfo { private readonly GRContext _grContext; private IGlPlatformSurfaceRenderTarget _surface; @@ -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 7f07f36de8..475f7da6b7 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -33,6 +33,7 @@ namespace Avalonia.Win32 private bool _multitouch; private TouchDevice _touchDevice = new TouchDevice(); private IInputRoot _owner; + private ManagedDeferredRendererLock _rendererLock = new ManagedDeferredRendererLock(); private bool _trackingMouse; private bool _decorated = true; private bool _resizable = true; @@ -150,7 +151,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) @@ -667,18 +670,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 && @@ -744,7 +755,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)