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/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 7e7e8fe47b..38f0df9a73 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/EglInterface.cs @@ -107,6 +107,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 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)