Pārlūkot izejas kodu

Merge pull request #2716 from AvaloniaUI/v0.8-pre

V0.8 pre
Nikita Tsukanov 6 gadi atpakaļ
vecāks
revīzija
146d51c992

+ 3 - 2
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()
+

+ 1 - 1
build/SharedVersion.props

@@ -2,7 +2,7 @@
   xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <Product>Avalonia</Product>
-    <Version>0.8.0</Version>
+    <Version>0.8.1</Version>
     <Copyright>Copyright 2018 &#169; The AvaloniaUI Project</Copyright>
     <PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
     <PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>

+ 8 - 0
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 =>
             {

+ 1 - 0
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@@ -10,6 +10,7 @@
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
+    <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001"/>
   </ItemGroup>
 
 

+ 4 - 1
samples/ControlCatalog.NetCore/Program.cs

@@ -3,7 +3,6 @@ using System.Diagnostics;
 using System.Linq;
 using System.Threading;
 using Avalonia;
-using Avalonia.Skia;
 
 namespace ControlCatalog.NetCore
 {
@@ -45,6 +44,10 @@ namespace ControlCatalog.NetCore
         public static AppBuilder BuildAvaloniaApp()
             => AppBuilder.Configure<App>()
                 .UsePlatformDetect()
+                .With(new Win32PlatformOptions
+                {
+                    AllowEglInitialization = true
+                })
                 .UseSkia()
                 .UseReactiveUI()
                 .UseDataGrid();

+ 0 - 2
src/Avalonia.Native/Avalonia.Native.csproj

@@ -7,8 +7,6 @@
     <CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath>
     <CastXmlPath Condition="Exists('/usr/local/bin/castxml')">/usr/local/bin/castxml</CastXmlPath>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <!-- This is needed because Rider doesn't see generated files in obj for some reason -->
-    <SharpGenGeneratedCodeFolder>$(MSBuildThisFileDirectory)/Generated</SharpGenGeneratedCodeFolder>
   </PropertyGroup>
 
   <ItemGroup Condition="'$(Configuration)' == 'Release' AND '$([MSBuild]::IsOSPlatform(OSX))' == 'true'">

+ 18 - 0
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<PlatformApi> AllowedPlatformApis = new List<PlatformApi>
+        {
+            PlatformApi.DirectX9
+        };
+    }
+}

+ 50 - 21
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<AngleOptions>()?.AllowedPlatformApis
+                              ?? new List<AngleOptions.PlatformApi> {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;

+ 27 - 6
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();
                 }

+ 26 - 0
src/Avalonia.OpenGL/EglInterface.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Runtime.InteropServices;
 using Avalonia.Platform;
 using Avalonia.Platform.Interop;
 
@@ -91,6 +92,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
     }
 }

+ 14 - 4
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<string, bool, IntPtr> 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<Utf8Buffer, IntPtr> 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")]

+ 6 - 1
src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs

@@ -6,4 +6,9 @@ namespace Avalonia.OpenGL
     {
         IGlPlatformSurfaceRenderingSession BeginDraw();
     }
-}
+
+    public interface IGlPlatformSurfaceRenderTargetWithCorruptionInfo : IGlPlatformSurfaceRenderTarget
+    {
+        bool IsCorrupted { get; }
+    }
+}

+ 5 - 0
src/Avalonia.Visuals/Platform/IRenderTarget.cs

@@ -23,4 +23,9 @@ namespace Avalonia.Platform
         /// </param>
         IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer);
     }
+
+    public interface IRenderTargetWithCorruptionInfo : IRenderTarget
+    {
+        bool IsCorrupted { get; }
+    }
 }

+ 5 - 0
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);

+ 14 - 0
src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs

@@ -7,11 +7,25 @@ namespace Avalonia.Rendering
     public class ManagedDeferredRendererLock : IDeferredRendererLock
     {
         private readonly object _lock = new object();
+        
+        /// <summary>
+        /// Tries to lock the target surface or window
+        /// </summary>
+        /// <returns>IDisposable if succeeded to obtain the lock</returns>
         public IDisposable TryLock()
         {
             if (Monitor.TryEnter(_lock))
                 return Disposable.Create(() => Monitor.Exit(_lock));
             return null;
         }
+
+        /// <summary>
+        /// Enters a waiting lock, only use from platform code, not from the renderer
+        /// </summary>
+        public IDisposable Lock()
+        {
+            Monitor.Enter(_lock);
+            return Disposable.Create(() => Monitor.Exit(_lock));
+        }
     }
 }

+ 32 - 0
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
+{
+    /// <summary>
+    /// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms 
+    /// </summary>
+    
+    public class UiThreadRenderTimer : DefaultRenderTimer
+    {
+        public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond)
+        {
+        }
+
+        protected override IDisposable StartCore(Action<TimeSpan> 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);
+        }
+    }
+}

+ 13 - 0
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<X11PlatformOptions>()
+                    ?.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,

+ 3 - 7
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;
+    }
     
 }

+ 8 - 0
src/Avalonia.X11/X11Platform.cs

@@ -95,6 +95,14 @@ namespace Avalonia
     {
         public bool UseEGL { get; set; }
         public bool UseGpu { get; set; } = true;
+
+        public List<string> GlxRendererBlacklist { get; set; } = new List<string>
+        {
+            // 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

+ 13 - 2
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)
                 {

+ 2 - 0
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();

+ 20 - 8
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)