瀏覽代碼

Merge pull request #773 from AvaloniaUI/render-loop

Render loop
Nikita Tsukanov 9 年之前
父節點
當前提交
f58dd2a894
共有 56 個文件被更改,包括 903 次插入286 次删除
  1. 43 0
      Avalonia.sln
  2. 14 0
      samples/RenderTest/App.config
  3. 6 0
      samples/RenderTest/App.xaml
  4. 16 0
      samples/RenderTest/App.xaml.cs
  5. 3 0
      samples/RenderTest/MainWindow.xaml
  6. 82 0
      samples/RenderTest/MainWindow.xaml.cs
  7. 35 0
      samples/RenderTest/Program.cs
  8. 36 0
      samples/RenderTest/Properties/AssemblyInfo.cs
  9. 192 0
      samples/RenderTest/RenderTest.csproj
  10. 26 0
      samples/RenderTest/RenderTest.v2.ncrunchproject
  11. 10 0
      samples/RenderTest/packages.config
  12. 3 2
      src/Android/Avalonia.Android/AndroidPlatform.cs
  13. 0 1
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  14. 0 59
      src/Android/Avalonia.Android/Platform/AndroidTopLevelRenderer.cs
  15. 0 1
      src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
  16. 0 3
      src/Avalonia.Controls/Application.cs
  17. 0 1
      src/Avalonia.Controls/Avalonia.Controls.csproj
  18. 0 66
      src/Avalonia.Controls/Platform/ITopLevelRenderer.cs
  19. 0 7
      src/Avalonia.Controls/Platform/PlatformManager.cs
  20. 23 8
      src/Avalonia.Controls/TopLevel.cs
  21. 1 1
      src/Avalonia.Controls/WrapPanel.cs
  22. 6 3
      src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj
  23. 1 1
      src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs
  24. 103 0
      src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs
  25. 19 0
      src/Avalonia.SceneGraph/Rendering/IRenderLoop.cs
  26. 0 31
      src/Avalonia.SceneGraph/Rendering/IRenderQueueManager.cs
  27. 22 3
      src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs
  28. 15 0
      src/Avalonia.SceneGraph/Rendering/IRenderer.cs
  29. 18 0
      src/Avalonia.SceneGraph/Rendering/IRendererFactory.cs
  30. 0 51
      src/Avalonia.SceneGraph/Rendering/RenderQueueManager.cs
  31. 67 0
      src/Avalonia.SceneGraph/Rendering/Renderer.cs
  32. 2 2
      src/Avalonia.SceneGraph/Rendering/RendererMixin.cs
  33. 1 1
      src/Avalonia.SceneGraph/Visual.cs
  34. 1 1
      src/Gtk/Avalonia.Cairo/CairoPlatform.cs
  35. 2 0
      src/Gtk/Avalonia.Gtk/GtkPlatform.cs
  36. 1 1
      src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs
  37. 1 1
      src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs
  38. 1 1
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  39. 9 2
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  40. 1 0
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  41. 8 2
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  42. 35 0
      src/Windows/Avalonia.Win32/RenderLoop.cs
  43. 2 0
      src/Windows/Avalonia.Win32/Win32Platform.cs
  44. 8 11
      src/Windows/Avalonia.Win32/WindowImpl.cs
  45. 13 1
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  46. 13 1
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  47. 0 1
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  48. 1 1
      tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
  49. 0 2
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  50. 13 12
      tests/Avalonia.LeakTests/ControlTests.cs
  51. 10 3
      tests/Avalonia.SceneGraph.UnitTests/TestRoot.cs
  52. 1 1
      tests/Avalonia.SceneGraph.UnitTests/VisualTree/MockRenderInterface.cs
  53. 11 1
      tests/Avalonia.UnitTests/TestRoot.cs
  54. 14 2
      tests/Avalonia.UnitTests/TestServices.cs
  55. 11 1
      tests/Avalonia.UnitTests/TestTemplatedRoot.cs
  56. 3 0
      tests/Avalonia.UnitTests/UnitTestApplication.cs

+ 43 - 0
Avalonia.sln

@@ -171,6 +171,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GtkInteropDemo", "samples\i
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
+EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
 EndProject
 Global
@@ -2363,6 +2365,46 @@ Global
 		{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Release|Mono.Build.0 = Release|Any CPU
 		{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Release|x86.ActiveCfg = Release|Any CPU
 		{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}.Release|x86.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|Mono.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Ad-Hoc|x86.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|Any CPU.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|iPhone.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|Mono.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|Mono.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|x86.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.AppStore|x86.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|Mono.ActiveCfg = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|Mono.Build.0 = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Debug|x86.Build.0 = Debug|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|iPhone.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Mono.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Mono.Build.0 = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|x86.ActiveCfg = Release|Any CPU
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|x86.Build.0 = Release|Any CPU
 		{29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
 		{29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
 		{29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
@@ -2474,6 +2516,7 @@ Global
 		{A0CC0258-D18C-4AB3-854F-7101680FC3F9} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{C7A69145-60B6-4882-97D6-A3921DD43978} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
 		{BD7F352C-6DC1-4740-BAF2-2D34A038728C} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
+		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 	EndGlobalSection
 EndGlobal

+ 14 - 0
samples/RenderTest/App.config

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 6 - 0
samples/RenderTest/App.xaml

@@ -0,0 +1,6 @@
+<Application xmlns="https://github.com/avaloniaui">
+  <Application.Styles>
+    <StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
+    <StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
+  </Application.Styles>
+</Application>

+ 16 - 0
samples/RenderTest/App.xaml.cs

@@ -0,0 +1,16 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia;
+using Avalonia.Markup.Xaml;
+
+namespace RenderTest
+{
+    public class App : Application
+    {
+        public override void Initialize()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 3 - 0
samples/RenderTest/MainWindow.xaml

@@ -0,0 +1,3 @@
+<Window xmlns="https://github.com/avaloniaui"
+        Title="Avalonia Render Test">
+</Window>

+ 82 - 0
samples/RenderTest/MainWindow.xaml.cs

@@ -0,0 +1,82 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Linq;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Data;
+using Avalonia.Layout;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Rendering;
+
+namespace RenderTest
+{
+    public class MainWindow : Window
+    {
+        public MainWindow()
+        {
+            this.InitializeComponent();
+            this.CreateAnimations();
+            this.AttachDevTools();
+            RendererMixin.DrawFpsCounter = true;
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+
+        private void CreateAnimations()
+        {
+            const int Count = 100;
+            var panel = new WrapPanel();
+
+            for (var i = 0; i < Count; ++i)
+            {
+                var element = new Panel
+                {
+                    Children =
+                    {
+                        new Ellipse
+                        {
+                            Width = 100,
+                            Height = 100,
+                            Fill = Brushes.Blue,
+                        },
+                        new Path
+                        {
+                            Data = StreamGeometry.Parse(
+                                "F1 M 16.6309,18.6563C 17.1309,8.15625 29.8809,14.1563 29.8809,14.1563C 30.8809,11.1563 34.1308,11.4063 34.1308,11.4063C 33.5,12 34.6309,13.1563 34.6309,13.1563C 32.1309,13.1562 31.1309,14.9062 31.1309,14.9062C 41.1309,23.9062 32.6309,27.9063 32.6309,27.9062C 24.6309,24.9063 21.1309,22.1562 16.6309,18.6563 Z M 16.6309,19.9063C 21.6309,24.1563 25.1309,26.1562 31.6309,28.6562C 31.6309,28.6562 26.3809,39.1562 18.3809,36.1563C 18.3809,36.1563 18,38 16.3809,36.9063C 15,36 16.3809,34.9063 16.3809,34.9063C 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z"),
+                            Fill = Brushes.Green,
+                            HorizontalAlignment = HorizontalAlignment.Center,
+                            VerticalAlignment = VerticalAlignment.Center,
+                            RenderTransform = new ScaleTransform(2, 2),
+                        }
+                    },
+                    Margin = new Thickness(4),
+                    RenderTransform = new ScaleTransform(),
+                };
+
+                var start = Animate.Stopwatch.Elapsed;
+                var index = i;
+                var degrees = Animate.Timer
+                    .Select(x => (x - start).TotalSeconds)
+                    .Where(x => (x % Count) >= index && (x % Count) < index + 1)
+                    .Select(x => (x % 1) / 1);
+
+                element.RenderTransform.Bind(
+                    ScaleTransform.ScaleXProperty,
+                    degrees,
+                    BindingPriority.Animation);
+
+                panel.Children.Add(element);
+            }
+
+            Content = panel;
+        }
+    }
+}

+ 35 - 0
samples/RenderTest/Program.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Logging.Serilog;
+using Avalonia.Platform;
+using Serilog;
+
+namespace RenderTest
+{
+    internal class Program
+    {
+        static void Main(string[] args)
+        {
+            InitializeLogging();
+
+            // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
+            // again.
+            AppBuilder.Configure<App>()
+                .UsePlatformDetect()
+                .Start<MainWindow>();
+        }
+
+        // This will be made into a runtime configuration extension soon!
+        private static void InitializeLogging()
+        {
+#if DEBUG
+            SerilogLogger.Initialize(new LoggerConfiguration()
+                .MinimumLevel.Warning()
+                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
+                .CreateLogger());
+#endif
+        }
+    }
+}

+ 36 - 0
samples/RenderTest/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("RenderTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("RenderTest")]
+[assembly: AssemblyCopyright("Copyright ©  2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f1fdc5b0-4654-416f-ae69-e3e9bbd87801")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 192 - 0
samples/RenderTest/RenderTest.csproj

@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>RenderTest</RootNamespace>
+    <AssemblyName>RenderTest</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <StartupObject />
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Serilog.FullNetFx, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Serilog.1.5.14\lib\net45\Serilog.FullNetFx.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Reactive.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Reactive.Core.3.0.0\lib\net45\System.Reactive.Core.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Interfaces, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Reactive.Interfaces.3.0.0\lib\net45\System.Reactive.Interfaces.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Linq, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Reactive.Linq.3.0.0\lib\net45\System.Reactive.Linq.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.PlatformServices, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Reactive.PlatformServices.3.0.0\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Windows.Threading, Version=3.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Reactive.Windows.Threading.3.0.0\lib\net45\System.Reactive.Windows.Threading.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+    <Reference Include="WindowsBase" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="App.xaml.cs">
+      <DependentUpon>App.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="MainWindow.xaml.cs">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="App.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
+      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
+      <Name>Avalonia.Animation</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
+      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
+      <Name>Avalonia.Base</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
+      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
+      <Name>Avalonia.Controls</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
+      <Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
+      <Name>Avalonia.DesignerSupport</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
+      <Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
+      <Name>Avalonia.Diagnostics</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
+      <Project>{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}</Project>
+      <Name>Avalonia.DotNetFrameworkRuntime</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
+      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
+      <Name>Avalonia.Input</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
+      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
+      <Name>Avalonia.Interactivity</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
+      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
+      <Name>Avalonia.Layout</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
+      <Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
+      <Name>Avalonia.Logging.Serilog</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
+      <Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
+      <Name>Avalonia.ReactiveUI</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.SceneGraph\Avalonia.SceneGraph.csproj">
+      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
+      <Name>Avalonia.SceneGraph</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
+      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
+      <Name>Avalonia.Styling</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
+      <Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
+      <Name>Avalonia.Themes.Default</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Gtk\Avalonia.Cairo\Avalonia.Cairo.csproj">
+      <Project>{fb05ac90-89ba-4f2f-a924-f37875fb547c}</Project>
+      <Name>Avalonia.Cairo</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Gtk\Avalonia.Gtk\Avalonia.Gtk.csproj">
+      <Project>{54f237d5-a70a-4752-9656-0c70b1a7b047}</Project>
+      <Name>Avalonia.Gtk</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
+      <Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
+      <Name>Avalonia.Markup.Xaml</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
+      <Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
+      <Name>Avalonia.Markup</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj">
+      <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
+      <Name>Avalonia.Direct2D1</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
+      <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
+      <Name>Avalonia.Win32</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="MainWindow.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 26 - 0
samples/RenderTest/RenderTest.v2.ncrunchproject

@@ -0,0 +1,26 @@
+<ProjectConfiguration>
+  <AutoDetectNugetBuildDependencies>true</AutoDetectNugetBuildDependencies>
+  <BuildPriority>1000</BuildPriority>
+  <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
+  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
+  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
+  <AllowCodeAnalysis>false</AllowCodeAnalysis>
+  <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
+  <RunPreBuildEvents>false</RunPreBuildEvents>
+  <RunPostBuildEvents>false</RunPostBuildEvents>
+  <PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
+  <InstrumentAssembly>true</InstrumentAssembly>
+  <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
+  <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
+  <DetectStackOverflow>true</DetectStackOverflow>
+  <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
+  <DefaultTestTimeout>60000</DefaultTestTimeout>
+  <UseBuildConfiguration />
+  <UseBuildPlatform />
+  <ProxyProcessPath />
+  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
+</ProjectConfiguration>

+ 10 - 0
samples/RenderTest/packages.config

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Serilog" version="1.5.14" targetFramework="net45" />
+  <package id="System.Reactive" version="3.0.0" targetFramework="net45" />
+  <package id="System.Reactive.Core" version="3.0.0" targetFramework="net45" />
+  <package id="System.Reactive.Interfaces" version="3.0.0" targetFramework="net45" />
+  <package id="System.Reactive.Linq" version="3.0.0" targetFramework="net45" />
+  <package id="System.Reactive.PlatformServices" version="3.0.0" targetFramework="net45" />
+  <package id="System.Reactive.Windows.Threading" version="3.0.0" targetFramework="net45" />
+</packages>

+ 3 - 2
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -1,6 +1,5 @@
 using System;
 using System.IO;
-using Avalonia.Android.CanvasRendering;
 using Avalonia.Android.Platform;
 using Avalonia.Android.Platform.Input;
 using Avalonia.Android.Platform.SkiaPlatform;
@@ -10,6 +9,7 @@ using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.Platform;
 using Avalonia.Shared.PlatformSupport;
+using Avalonia.Skia;
 
 namespace Avalonia
 {
@@ -50,8 +50,9 @@ namespace Avalonia.Android
                 .Bind<IPlatformSettings>().ToConstant(Instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
                 .Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
-                .Bind<ITopLevelRenderer>().ToTransient<AndroidTopLevelRenderer>()
                 .Bind<IWindowingPlatform>().ToConstant(Instance);
+
+            SkiaPlatform.Initialize();
         }
 
         public void Init(Type applicationType)

+ 0 - 1
src/Android/Avalonia.Android/Avalonia.Android.csproj

@@ -74,7 +74,6 @@
     <Compile Include="Platform\Specific\AvaloniaActivity.cs" />
     <Compile Include="Platform\Specific\Helpers\AndroidTouchEventsHelper.cs" />
     <Compile Include="Platform\Specific\Helpers\AndroidKeyboardEventsHelper.cs" />
-    <Compile Include="Platform\AndroidTopLevelRenderer.cs" />
     <Compile Include="Resources\Resource.Designer.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="RuntimeInfo.cs" />

+ 0 - 59
src/Android/Avalonia.Android/Platform/AndroidTopLevelRenderer.cs

@@ -1,59 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Controls.Platform;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.Threading;
-using System;
-using System.Collections.Generic;
-
-namespace Avalonia.Android.CanvasRendering
-{
-    internal class AndroidTopLevelRenderer : ITopLevelRenderer
-    {
-        public void Attach(TopLevel topLevel)
-        {
-            var resources = new List<IDisposable>();
-            var initialClientSize = topLevel.PlatformImpl.ClientSize;
-
-
-            var queueManager = ((IRenderRoot)topLevel).RenderQueueManager;
-
-            if (queueManager == null)
-                return;
-
-
-            var viewport = PlatformManager.CreateRenderTarget(topLevel.PlatformImpl);
-            resources.Add(viewport);
-            //resources.Add(queueManager.RenderNeeded.Subscribe(_
-            //    =>
-            //    Dispatcher.UIThread.InvokeAsync(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize)))));
-            Action pendingInvalidation = null;
-            resources.Add(queueManager.RenderNeeded.Subscribe(_ =>
-            {
-                if (pendingInvalidation == null)
-                {
-                    pendingInvalidation = () =>
-                    {
-                        topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize));
-                        pendingInvalidation = null;
-                    };
-                    Dispatcher.UIThread.InvokeAsync(pendingInvalidation);
-                }
-            }
-            ));
-
-            topLevel.PlatformImpl.Paint = rect =>
-            {
-                viewport.Render(topLevel);
-                queueManager.RenderFinished();
-            };
-
-            topLevel.Closed += delegate
-            {
-                foreach (var disposable in resources)
-                    disposable.Dispose();
-                resources.Clear();
-            };
-        }
-    }
-}

+ 0 - 1
src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs

@@ -26,6 +26,5 @@ namespace Avalonia.Platform
         bool CurrentThreadIsLoopThread { get; }
 
         event Action Signaled;
-
     }
 }

+ 0 - 3
src/Avalonia.Controls/Application.cs

@@ -2,10 +2,8 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Reflection;
 using System.Threading;
 using Avalonia.Controls;
-using Avalonia.Controls.Platform;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
@@ -177,7 +175,6 @@ namespace Avalonia
                 .Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
                 .Bind<IStyler>().ToConstant(_styler)
                 .Bind<ILayoutManager>().ToSingleton<LayoutManager>()
-                .Bind<IRenderQueueManager>().ToTransient<RenderQueueManager>()
                 .Bind<IApplicationLifecycle>().ToConstant(this);
         }
     }

+ 0 - 1
src/Avalonia.Controls/Avalonia.Controls.csproj

@@ -73,7 +73,6 @@
     <Compile Include="Mixins\ContentControlMixin.cs" />
     <Compile Include="Platform\IWindowIconImpl.cs" />
     <Compile Include="Platform\IPlatformIconLoader.cs" />
-    <Compile Include="Platform\ITopLevelRenderer.cs" />
     <Compile Include="Platform\IWindowingPlatform.cs" />
     <Compile Include="Platform\PlatformManager.cs" />
     <Compile Include="Presenters\IContentPresenterHost.cs" />

+ 0 - 66
src/Avalonia.Controls/Platform/ITopLevelRenderer.cs

@@ -1,66 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.Threading;
-
-namespace Avalonia.Controls.Platform
-{
-    public interface ITopLevelRenderer
-    {
-        void Attach(TopLevel topLevel);
-    }
-
-
-    class DefaultTopLevelRenderer : ITopLevelRenderer
-    {
-
-        public void Attach(TopLevel topLevel)
-        {
-            var resources = new List<IDisposable>();
-            var initialClientSize = topLevel.PlatformImpl.ClientSize;
-
-
-            var queueManager = ((IRenderRoot)topLevel).RenderQueueManager;
-
-            if (queueManager == null)
-                return;
-
-
-            var viewport = PlatformManager.CreateRenderTarget(topLevel.PlatformImpl);
-            resources.Add(viewport);
-            resources.Add(queueManager.RenderNeeded.Subscribe(_
-                =>
-                Dispatcher.UIThread.InvokeAsync(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize)))));
-
-            topLevel.PlatformImpl.Paint = rect =>
-            {
-                try
-                {
-                    viewport.Render(topLevel);
-                }
-                catch (RenderTargetCorruptedException ex)
-                {
-                    Logging.Logger.Error("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
-                    viewport.Dispose();
-                    resources.Remove(viewport);
-                    viewport = PlatformManager.CreateRenderTarget(topLevel.PlatformImpl);
-                    resources.Add(viewport);
-                    topLevel.PlatformImpl.Paint(rect); // Retry painting
-                }
-                queueManager.RenderFinished();
-            };
-
-            topLevel.Closed += delegate
-            {
-                foreach (var disposable in resources)
-                    disposable.Dispose();
-                resources.Clear();
-            };
-
-        }
-    }
-}

+ 0 - 7
src/Avalonia.Controls/Platform/PlatformManager.cs

@@ -12,13 +12,6 @@ namespace Avalonia.Controls.Platform
 
         static bool s_designerMode;
 
-        public static IRenderTarget CreateRenderTarget(ITopLevelImpl window)
-        {
-            return AvaloniaLocator.Current
-                .GetService<IPlatformRenderInterface>()
-                .CreateRenderer(window.Handle);
-        }
-
         public static IDisposable DesignerMode()
         {
             s_designerMode = true;

+ 23 - 8
src/Avalonia.Controls/TopLevel.cs

@@ -45,11 +45,11 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<IInputElement> PointerOverElementProperty =
             AvaloniaProperty.Register<TopLevel, IInputElement>(nameof(IInputRoot.PointerOverElement));
 
-        private readonly IRenderQueueManager _renderQueueManager;
         private readonly IInputManager _inputManager;
         private readonly IAccessKeyHandler _accessKeyHandler;
         private readonly IKeyboardNavigationHandler _keyboardNavigationHandler;
         private readonly IApplicationLifecycle _applicationLifecycle;
+        private readonly IPlatformRenderInterface _renderInterface;
         private Size _clientSize;
         private bool _isActive;
 
@@ -86,22 +86,25 @@ namespace Avalonia.Controls
             }
 
             PlatformImpl = impl;
-
             dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current;
             var styler = TryGetService<IStyler>(dependencyResolver);
+
             _accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver);
             _inputManager = TryGetService<IInputManager>(dependencyResolver);
             _keyboardNavigationHandler = TryGetService<IKeyboardNavigationHandler>(dependencyResolver);
-            _renderQueueManager = TryGetService<IRenderQueueManager>(dependencyResolver);
             _applicationLifecycle = TryGetService<IApplicationLifecycle>(dependencyResolver);
+            _renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
 
-            (dependencyResolver.GetService<ITopLevelRenderer>() ?? new DefaultTopLevelRenderer()).Attach(this);
+            var renderLoop = TryGetService<IRenderLoop>(dependencyResolver);
+            var rendererFactory = TryGetService<IRendererFactory>(dependencyResolver);
+            Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
 
             PlatformImpl.SetInputRoot(this);
             PlatformImpl.Activated = HandleActivated;
             PlatformImpl.Deactivated = HandleDeactivated;
             PlatformImpl.Closed = HandleClosed;
             PlatformImpl.Input = HandleInput;
+            PlatformImpl.Paint = Renderer != null ? (Action<Rect>)Renderer.Render : null;
             PlatformImpl.Resized = HandleResized;
             PlatformImpl.ScalingChanged = HandleScalingChanged;
             PlatformImpl.PositionChanged = HandlePositionChanged;
@@ -179,9 +182,9 @@ namespace Avalonia.Controls
         }
         
         /// <summary>
-        /// Gets the window render manager.
+        /// Gets the renderer for the window.
         /// </summary>
-        IRenderQueueManager IRenderRoot.RenderQueueManager => _renderQueueManager;
+        public IRenderer Renderer { get; }
 
         /// <summary>
         /// Gets the access key handler for the window.
@@ -231,6 +234,18 @@ namespace Avalonia.Controls
             private set;
         }
 
+        /// <inheritdoc/>
+        IRenderTarget IRenderRoot.CreateRenderTarget()
+        {
+            return _renderInterface.CreateRenderTarget(PlatformImpl.Handle);
+        }
+
+        /// <inheritdoc/>
+        void IRenderRoot.Invalidate(Rect rect)
+        {
+            PlatformImpl.Invalidate(rect);
+        }
+
         /// <inheritdoc/>
         Point IRenderRoot.PointToClient(Point p)
         {
@@ -321,8 +336,8 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, throwing an
-        /// exception if not found.
+        /// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a
+        /// warning if not found.
         /// </summary>
         /// <typeparam name="T">The service type.</typeparam>
         /// <param name="resolver">The resolver.</param>

+ 1 - 1
src/Avalonia.Controls/WrapPanel.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Controls
     /// Subsequent ordering happens sequentially from top to bottom or from right to left, 
     /// depending on the value of the Orientation property.
     /// </summary>
-    internal class WrapPanel : Panel, INavigableContainer
+    public class WrapPanel : Panel, INavigableContainer
     {
         /// <summary>
         /// Defines the <see cref="Orientation"/> property.

+ 6 - 3
src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj

@@ -102,6 +102,12 @@
     <Compile Include="Media\Geometry.cs" />
     <Compile Include="Media\IDrawingContext.cs" />
     <Compile Include="Platform\ExportRenderingSubsystemAttribute.cs" />
+    <Compile Include="Rendering\IRenderer.cs" />
+    <Compile Include="Rendering\IRendererFactory.cs" />
+    <Compile Include="Rendering\IRenderLoop.cs" />
+    <Compile Include="Rendering\Renderer.cs" />
+    <Compile Include="Rendering\RendererMixin.cs" />
+    <Compile Include="Rendering\DefaultRenderLoop.cs" />
     <Compile Include="RenderTargetCorruptedException.cs" />
     <Compile Include="VisualTree\IVisual.cs" />
     <Compile Include="Media\Imaging\Bitmap.cs" />
@@ -139,9 +145,6 @@
     <Compile Include="RelativeRect.cs" />
     <Compile Include="Rect.cs" />
     <Compile Include="Rendering\IRenderRoot.cs" />
-    <Compile Include="Rendering\IRenderQueueManager.cs" />
-    <Compile Include="Rendering\RendererMixin.cs" />
-    <Compile Include="Rendering\RenderQueueManager.cs" />
     <Compile Include="Size.cs" />
     <Compile Include="Thickness.cs" />
     <Compile Include="Vector.cs" />

+ 1 - 1
src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs

@@ -42,7 +42,7 @@ namespace Avalonia.Platform
         /// </summary>
         /// <param name="handle">The platform handle for the renderer.</param>
         /// <returns>An <see cref="IRenderTarget"/>.</returns>
-        IRenderTarget CreateRenderer(IPlatformHandle handle);
+        IRenderTarget CreateRenderTarget(IPlatformHandle handle);
 
         /// <summary>
         /// Creates a render target bitmap implementation.

+ 103 - 0
src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs

@@ -0,0 +1,103 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering
+{
+    /// <summary>
+    /// Defines a default render loop that uses a standard timer.
+    /// </summary>
+    /// <remarks>
+    /// This class may be overridden by platform implementations to use a specialized timer
+    /// implementation.
+    /// </remarks>
+    public class DefaultRenderLoop : IRenderLoop
+    {
+        private IPlatformThreadingInterface _threading;
+        private int _subscriberCount;
+        private EventHandler<EventArgs> _tick;
+        private IDisposable _subscription;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DefaultRenderLoop"/> class.
+        /// </summary>
+        /// <param name="framesPerSecond">
+        /// The number of frames per second at which the loop should run.
+        /// </param>
+        public DefaultRenderLoop(int framesPerSecond)
+        {
+            FramesPerSecond = framesPerSecond;
+        }
+
+        /// <summary>
+        /// Gets the number of frames per second at which the loop runs.
+        /// </summary>
+        public int FramesPerSecond { get; }
+
+        /// <inheritdoc/>
+        public event EventHandler<EventArgs> Tick
+        {
+            add
+            {
+                if (_subscriberCount++ == 0)
+                {
+                    Start();
+                }
+
+                _tick += value;
+            }
+
+            remove
+            {
+                if (--_subscriberCount == 0)
+                {
+                    Stop();
+                }
+
+                _tick -= value;
+            }
+        }
+
+        /// <summary>
+        /// Starts the timer.
+        /// </summary>
+        protected void Start()
+        {
+            _subscription = StartCore(InternalTick);
+        }
+
+        /// <summary>
+        /// Provides the implementation of starting the timer.
+        /// </summary>
+        /// <param name="tick">The method to call on each tick.</param>
+        /// <remarks>
+        /// This can be overridden by platform implementations to use a specialized timer
+        /// implementation.
+        /// </remarks>
+        protected virtual IDisposable StartCore(Action tick)
+        {
+            if (_threading == null)
+            {
+                _threading = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
+            }
+
+            return _threading.StartTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick);
+        }
+
+        /// <summary>
+        /// Stops the timer.
+        /// </summary>
+        protected void Stop()
+        {
+            _subscription.Dispose();
+            _subscription = null;
+        }
+
+        private void InternalTick()
+        {
+            _tick(this, EventArgs.Empty);
+        }
+    }
+}

+ 19 - 0
src/Avalonia.SceneGraph/Rendering/IRenderLoop.cs

@@ -0,0 +1,19 @@
+using System;
+
+namespace Avalonia.Rendering
+{
+    /// <summary>
+    /// Defines the interface implemented by an application render loop.
+    /// </summary>
+    public interface IRenderLoop
+    {
+        /// <summary>
+        /// Raised when the render loop ticks to signal a new frame should be drawn.
+        /// </summary>
+        /// <remarks>
+        /// This event can be raised on any thread; it is the responsibility of the subscriber to
+        /// switch execution to the right thread.
+        /// </remarks>
+        event EventHandler<EventArgs> Tick;
+    }
+}

+ 0 - 31
src/Avalonia.SceneGraph/Rendering/IRenderQueueManager.cs

@@ -1,31 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering
-{
-    /// <summary>
-    /// Defines the interface for a <see cref="RenderQueueManager"/>.
-    /// </summary>
-    public interface IRenderQueueManager
-    {
-        /// <summary>
-        /// Gets an observable that is fired whenever a render is required.
-        /// </summary>
-        IObservable<Unit> RenderNeeded { get; }
-
-        /// <summary>
-        /// Invalidates the render for the specified visual and raises <see cref="RenderNeeded"/>.
-        /// </summary>
-        /// <param name="visual">The visual.</param>
-        void InvalidateRender(IVisual visual);
-
-        /// <summary>
-        /// Called when rendering is finished.
-        /// </summary>
-        void RenderFinished();
-    }
-}

+ 22 - 3
src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs

@@ -1,19 +1,38 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using Avalonia.Platform;
+using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering
 {
     /// <summary>
     /// Represents the root of a renderable tree.
     /// </summary>
-    public interface IRenderRoot
+    public interface IRenderRoot : IVisual
     {
         /// <summary>
-        /// Gets the render manager which schedules renders.
+        /// Gets the client size of the window.
         /// </summary>
-        IRenderQueueManager RenderQueueManager { get; }
+        Size ClientSize { get; }
+
+        /// <summary>
+        /// Gets the renderer for the window.
+        /// </summary>
+        IRenderer Renderer { get; }
+
+        /// <summary>
+        /// Creates a render target for the window.
+        /// </summary>
+        /// <returns>An <see cref="IRenderTarget"/>.</returns>
+        IRenderTarget CreateRenderTarget();
+
+        /// <summary>
+        /// Adds a rectangle to the window's dirty region.
+        /// </summary>
+        /// <param name="rect">The rectangle.</param>
+        void Invalidate(Rect rect);
 
         /// <summary>
         /// Converts a point from screen to client coordinates.

+ 15 - 0
src/Avalonia.SceneGraph/Rendering/IRenderer.cs

@@ -0,0 +1,15 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering
+{
+    public interface IRenderer : IDisposable
+    {
+        void AddDirty(IVisual visual);
+
+        void Render(Rect rect);
+    }
+}

+ 18 - 0
src/Avalonia.SceneGraph/Rendering/IRendererFactory.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace Avalonia.Rendering
+{
+    /// <summary>
+    /// Defines a factory for creating <see cref="IRenderer"/> instances.
+    /// </summary>
+    public interface IRendererFactory
+    {
+        /// <summary>
+        /// Creates a new renderer for the specified render root.
+        /// </summary>
+        /// <param name="root">The render root.</param>
+        /// <param name="renderLoop">The render loop.</param>
+        /// <returns>An instance of an <see cref="IRenderer"/>.</returns>
+        IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop);
+    }
+}

+ 0 - 51
src/Avalonia.SceneGraph/Rendering/RenderQueueManager.cs

@@ -1,51 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive;
-using System.Reactive.Subjects;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Rendering
-{
-    /// <summary>
-    /// Schedules the rendering of a tree.
-    /// </summary>
-    public class RenderQueueManager : IRenderQueueManager
-    {
-        private readonly Subject<Unit> _renderNeeded = new Subject<Unit>();
-
-        private bool _renderQueued;
-
-        /// <summary>
-        /// Gets an observable that is fired whenever a render is required.
-        /// </summary>
-        public IObservable<Unit> RenderNeeded => _renderNeeded;
-
-        /// <summary>
-        /// Gets a value indicating whether a render is queued.
-        /// </summary>
-        public bool RenderQueued => _renderQueued;
-
-        /// <summary>
-        /// Invalidates the render for the specified visual and raises <see cref="RenderNeeded"/>.
-        /// </summary>
-        /// <param name="visual">The visual.</param>
-        public void InvalidateRender(IVisual visual)
-        {
-            if (!_renderQueued)
-            {
-                _renderQueued = true;
-                _renderNeeded.OnNext(Unit.Default);
-            }
-        }
-
-        /// <summary>
-        /// Called when rendering is finished.
-        /// </summary>
-        public void RenderFinished()
-        {
-            _renderQueued = false;
-        }
-    }
-}

+ 67 - 0
src/Avalonia.SceneGraph/Rendering/Renderer.cs

@@ -0,0 +1,67 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia.Platform;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering
+{
+    public class Renderer : IDisposable, IRenderer
+    {
+        private readonly IRenderLoop _renderLoop;
+        private readonly IRenderRoot _root;
+        private IRenderTarget _renderTarget;
+        private bool _dirty;
+
+        public Renderer(IRenderRoot root, IRenderLoop renderLoop)
+        {
+            Contract.Requires<ArgumentNullException>(root != null);
+
+            _root = root;
+            _renderLoop = renderLoop;
+            _renderLoop.Tick += OnRenderLoopTick;
+        }
+
+        public void AddDirty(IVisual visual)
+        {
+            _dirty = true;
+        }
+
+        public void Dispose()
+        {
+            _renderLoop.Tick -= OnRenderLoopTick;
+        }
+
+        public void Render(Rect rect)
+        {
+            if (_renderTarget == null)
+            {
+                _renderTarget = _root.CreateRenderTarget();
+            }
+
+            try
+            {
+                _renderTarget.Render(_root);
+            }
+            catch (RenderTargetCorruptedException ex)
+            {
+                Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
+                _renderTarget.Dispose();
+                _renderTarget = null;
+            }
+            finally
+            {
+                _dirty = false;
+            }
+        }
+
+        private void OnRenderLoopTick(object sender, EventArgs e)
+        {
+            if (_dirty)
+            {
+                _root.Invalidate(new Rect(_root.ClientSize));
+            }
+        }
+    }
+}

+ 2 - 2
src/Avalonia.SceneGraph/Rendering/RendererMixin.cs

@@ -1,4 +1,4 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
+// Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
@@ -51,7 +51,7 @@ namespace Avalonia.Rendering
                     s_currentFrames++;
                     var now = s_stopwatch.Elapsed;
                     var elapsed = now - s_lastMeasure;
-                    if (elapsed.TotalSeconds > 0)
+                    if (elapsed.TotalSeconds > 1)
                     {
                         s_fps = (int) (s_currentFrames/elapsed.TotalSeconds);
                         s_currentFrames = 0;

+ 1 - 1
src/Avalonia.SceneGraph/Visual.cs

@@ -259,7 +259,7 @@ namespace Avalonia
         /// </summary>
         public void InvalidateVisual()
         {
-            VisualRoot?.RenderQueueManager?.InvalidateRender(this);
+            VisualRoot?.Renderer?.AddDirty(this);
         }
 
         /// <summary>

+ 1 - 1
src/Gtk/Avalonia.Cairo/CairoPlatform.cs

@@ -50,7 +50,7 @@ namespace Avalonia.Cairo
             return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight);
         }
 
-        public IRenderTarget CreateRenderer(IPlatformHandle handle)
+        public IRenderTarget CreateRenderTarget(IPlatformHandle handle)
         {
             var window = handle as Gtk.Window;
             if (window != null)

+ 2 - 0
src/Gtk/Avalonia.Gtk/GtkPlatform.cs

@@ -25,6 +25,7 @@ namespace Avalonia
 namespace Avalonia.Gtk
 {
     using System.IO;
+    using Rendering;
     using Gtk = global::Gtk;
 
     public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader
@@ -53,6 +54,7 @@ namespace Avalonia.Gtk
                 .Bind<IMouseDevice>().ToConstant(GtkMouseDevice.Instance)
                 .Bind<IPlatformSettings>().ToConstant(s_instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
+                .Bind<IRenderLoop>().ToConstant(new DefaultRenderLoop(60))
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>()
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance);
             _uiThread = Thread.CurrentThread;

+ 1 - 1
src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs

@@ -22,7 +22,7 @@ namespace Avalonia.Skia.Android
         {
             _renderTarget =
                 AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
-                    .CreateRenderer(this);
+                    .CreateRenderTarget(this);
         }
 
         protected override void Draw()

+ 1 - 1
src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs

@@ -21,7 +21,7 @@ namespace Avalonia.Skia.iOS.TestApp
             AutoresizingMask = UIViewAutoresizing.All;
             SkiaPlatform.Initialize();
             _target = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
-                .CreateRenderer(AvaloniaPlatformHandle);
+                .CreateRenderTarget(AvaloniaPlatformHandle);
             UpdateText(0);
         }
         double _radians = 0;

+ 1 - 1
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -58,7 +58,7 @@ namespace Avalonia.Skia
             return new BitmapImpl(width, height);
         }
 
-        public IRenderTarget CreateRenderer(IPlatformHandle handle)
+        public IRenderTarget CreateRenderTarget(IPlatformHandle handle)
         {
             return new WindowRenderTarget(handle);
         }

+ 9 - 2
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -7,6 +7,7 @@ using Avalonia.Direct2D1.Media;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Controls;
+using Avalonia.Rendering;
 
 namespace Avalonia
 {
@@ -22,7 +23,7 @@ namespace Avalonia
 
 namespace Avalonia.Direct2D1
 {
-    public class Direct2D1Platform : IPlatformRenderInterface
+    public class Direct2D1Platform : IPlatformRenderInterface, IRendererFactory
     {
         private static readonly Direct2D1Platform s_instance = new Direct2D1Platform();
 
@@ -38,6 +39,7 @@ namespace Avalonia.Direct2D1
 
         public static void Initialize() => AvaloniaLocator.CurrentMutable
             .Bind<IPlatformRenderInterface>().ToConstant(s_instance)
+            .Bind<IRendererFactory>().ToConstant(s_instance)
             .BindToSelf(s_d2D1Factory)
             .BindToSelf(s_dwfactory)
             .BindToSelf(s_imagingFactory);
@@ -59,7 +61,12 @@ namespace Avalonia.Direct2D1
             return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight, wrapping);
         }
 
-        public IRenderTarget CreateRenderer(IPlatformHandle handle)
+        public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
+        {
+            return new Renderer(root, renderLoop);
+        }
+
+        public IRenderTarget CreateRenderTarget(IPlatformHandle handle)
         {
             if (handle.HandleDescriptor == "HWND")
             {

+ 1 - 0
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@@ -70,6 +70,7 @@
     </Compile>
     <Compile Include="Embedding\WpfAvaloniaControlHost.cs" />
     <Compile Include="IconImpl.cs" />
+    <Compile Include="RenderLoop.cs" />
     <Compile Include="SystemDialogImpl.cs" />
     <Compile Include="CursorFactory.cs" />
     <Compile Include="Input\KeyInterop.cs" />

+ 8 - 2
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -23,6 +23,8 @@ namespace Avalonia.Win32.Interop
 
         public delegate void TimerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime);
 
+        public delegate void TimeCallback(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2);
+
         public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
 
         public enum Cursor
@@ -705,8 +707,6 @@ namespace Avalonia.Win32.Interop
         [DllImport("user32.dll")]
         public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName);
 
-
-
         [DllImport("user32.dll")]
         public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
         
@@ -738,6 +738,12 @@ namespace Avalonia.Win32.Interop
         [DllImport("user32.dll")]
         public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow);
 
+        [DllImport("Winmm.dll")]
+        public static extern uint timeKillEvent(uint uTimerID);
+
+        [DllImport("Winmm.dll")]
+        public static extern uint timeSetEvent(uint uDelay, uint uResolution, TimeCallback lpTimeProc, UIntPtr dwUser, uint fuEvent);
+
         [DllImport("user32.dll")]
         public static extern int ToUnicode(
             uint virtualKeyCode,

+ 35 - 0
src/Windows/Avalonia.Win32/RenderLoop.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Reactive.Disposables;
+using Avalonia.Rendering;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+    internal class RenderLoop : DefaultRenderLoop
+    {
+        private UnmanagedMethods.TimeCallback timerDelegate;
+
+        public RenderLoop(int framesPerSecond)
+            : base(framesPerSecond)
+        {
+        }
+
+        protected override IDisposable StartCore(Action tick)
+        {
+            timerDelegate = (id, uMsg, user, dw1, dw2) => tick();
+
+            var handle = UnmanagedMethods.timeSetEvent(
+                (uint)(1000 / FramesPerSecond),
+                0,
+                timerDelegate,
+                UIntPtr.Zero,
+                1);
+
+            return Disposable.Create(() =>
+            {
+                timerDelegate = null;
+                UnmanagedMethods.timeKillEvent(handle);
+            });
+        }
+    }
+}

+ 2 - 0
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -16,6 +16,7 @@ using Avalonia.Win32.Input;
 using Avalonia.Win32.Interop;
 using Avalonia.Controls;
 using System.IO;
+using Avalonia.Rendering;
 
 namespace Avalonia
 {
@@ -65,6 +66,7 @@ namespace Avalonia.Win32
                 .Bind<IMouseDevice>().ToConstant(WindowsMouseDevice.Instance)
                 .Bind<IPlatformSettings>().ToConstant(s_instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
+                .Bind<IRenderLoop>().ToConstant(new RenderLoop(60))
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>()
                 .Bind<IWindowingPlatform>().ToConstant(s_instance)
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance);

+ 8 - 11
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -537,18 +537,15 @@ namespace Avalonia.Win32
                     break;
 
                 case UnmanagedMethods.WindowsMessage.WM_PAINT:
-                    if (Paint != null)
-                    {
-                        UnmanagedMethods.PAINTSTRUCT ps;
+                    UnmanagedMethods.PAINTSTRUCT ps;
 
-                        if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero)
-                        {
-                            UnmanagedMethods.RECT r;
-                            UnmanagedMethods.GetUpdateRect(_hwnd, out r, false);
-                            var f = Scaling;
-                            Paint(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, (r.bottom - r.top) / f));
-                            UnmanagedMethods.EndPaint(_hwnd, ref ps);
-                        }
+                    if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero)
+                    {
+                        UnmanagedMethods.RECT r;
+                        UnmanagedMethods.GetUpdateRect(_hwnd, out r, false);
+                        var f = Scaling;
+                        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;

+ 13 - 1
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@@ -10,6 +10,7 @@ using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Layout;
+using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.UnitTests;
 using Xunit;
@@ -315,7 +316,18 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
         private class TestScroller : ScrollContentPresenter, IRenderRoot
         {
-            public IRenderQueueManager RenderQueueManager { get; }
+            public IRenderer Renderer { get; }
+            public Size ClientSize { get; }
+
+            public IRenderTarget CreateRenderTarget()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void Invalidate(Rect rect)
+            {
+                throw new NotImplementedException();
+            }
 
             public Point PointToClient(Point point)
             {

+ 13 - 1
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

@@ -12,6 +12,7 @@ using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
+using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.UnitTests;
 using Xunit;
@@ -990,7 +991,18 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
         private class TestScroller : ScrollContentPresenter, IRenderRoot
         {
-            public IRenderQueueManager RenderQueueManager { get; }
+            public IRenderer Renderer { get; }
+            public Size ClientSize { get; }
+
+            public IRenderTarget CreateRenderTarget()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void Invalidate(Rect rect)
+            {
+                throw new NotImplementedException();
+            }
 
             public Point PointToClient(Point point)
             {

+ 0 - 1
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@@ -268,7 +268,6 @@ namespace Avalonia.Controls.UnitTests.Primitives
             globalStyles.Setup(x => x.Styles).Returns(styles);
 
             var renderInterface = new Mock<IPlatformRenderInterface>();
-            renderInterface.Setup(x => x.CreateRenderer(It.IsAny<IPlatformHandle>())).Returns(() => new Mock<IRenderTarget>().Object);
 
             AvaloniaLocator.CurrentMutable
                 .Bind<ILayoutManager>().ToTransient<LayoutManager>()

+ 1 - 1
tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs

@@ -340,7 +340,7 @@ namespace Avalonia.Input.UnitTests
                 throw new NotImplementedException();
             }
 
-            public IRenderTarget CreateRenderer(IPlatformHandle handle)
+            public IRenderTarget CreateRenderTarget(IPlatformHandle handle)
             {
                 throw new NotImplementedException();
             }

+ 0 - 2
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@@ -137,7 +137,6 @@ namespace Avalonia.Layout.UnitTests
             var formattedText = fixture.Create<IFormattedTextImpl>();
             var globalStyles = new Mock<IGlobalStyles>();
             var renderInterface = fixture.Create<IPlatformRenderInterface>();
-            var renderManager = fixture.Create<IRenderQueueManager>();
             var windowImpl = new Mock<IWindowImpl>();
 
             windowImpl.SetupProperty(x => x.ClientSize);
@@ -151,7 +150,6 @@ namespace Avalonia.Layout.UnitTests
                 .Bind<ILayoutManager>().ToConstant(new LayoutManager())
                 .Bind<IRuntimePlatform>().ToConstant(new AppBuilder().RuntimePlatform)
                 .Bind<IPlatformRenderInterface>().ToConstant(renderInterface)
-                .Bind<IRenderQueueManager>().ToConstant(renderManager)
                 .Bind<IStyler>().ToConstant(new Styler())
                 .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock(() => windowImpl.Object));
 

+ 13 - 12
tests/Avalonia.LeakTests/ControlTests.cs

@@ -11,9 +11,11 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Diagnostics;
 using Avalonia.Layout;
+using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
+using Moq;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -52,6 +54,7 @@ namespace Avalonia.LeakTests
                 };
 
                 var result = run();
+                PurgeMoqReferences();
 
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
@@ -87,6 +90,7 @@ namespace Avalonia.LeakTests
                 };
 
                 var result = run();
+                PurgeMoqReferences();
 
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
@@ -123,6 +127,7 @@ namespace Avalonia.LeakTests
                 };
 
                 var result = run();
+                PurgeMoqReferences();
 
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@@ -158,6 +163,7 @@ namespace Avalonia.LeakTests
                 };
 
                 var result = run();
+                PurgeMoqReferences();
 
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@@ -201,6 +207,7 @@ namespace Avalonia.LeakTests
                 };
 
                 var result = run();
+                PurgeMoqReferences();
 
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@@ -287,25 +294,19 @@ namespace Avalonia.LeakTests
                 };
 
                 var result = run();
+                PurgeMoqReferences();
 
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
             }
         }
 
-        private class TestTemplatedControl : TemplatedControl
+        private static void PurgeMoqReferences()
         {
-            public static readonly StyledProperty<int> IsCanvasVisibleProperty =
-                AvaloniaProperty.Register<TestTemplatedControl, int>("IsCanvasVisible");
-
-            public TestTemplatedControl()
-            {
-                Template = new FuncControlTemplate<TestTemplatedControl>(parent =>
-                    new Canvas
-                    {
-                        [~IsVisibleProperty] = parent[~IsCanvasVisibleProperty]
-                    });
-            }
+            // Moq holds onto references in its mock of IRenderer in case we want to check if a method has been called;
+            // clear these.
+            var renderer = Mock.Get(AvaloniaLocator.Current.GetService<IRenderer>());
+            renderer.ResetCalls();
         }
 
         private class Node

+ 10 - 3
tests/Avalonia.SceneGraph.UnitTests/TestRoot.cs

@@ -9,12 +9,19 @@ namespace Avalonia.SceneGraph.UnitTests
 {
     public class TestRoot : TestVisual, IRenderRoot
     {
-        public IRenderTarget RenderTarget
+        public Size ClientSize { get; }
+        
+        public IRenderTarget CreateRenderTarget()
         {
-            get { throw new NotImplementedException(); }
+            throw new NotImplementedException();
+        }
+
+        public void Invalidate(Rect rect)
+        {
+            throw new NotImplementedException();
         }
 
-        public IRenderQueueManager RenderQueueManager
+        public IRenderer Renderer
         {
             get { throw new NotImplementedException(); }
         }

+ 1 - 1
tests/Avalonia.SceneGraph.UnitTests/VisualTree/MockRenderInterface.cs

@@ -20,7 +20,7 @@ namespace Avalonia.SceneGraph.UnitTests.VisualTree
             throw new NotImplementedException();
         }
 
-        public IRenderTarget CreateRenderer(IPlatformHandle handle)
+        public IRenderTarget CreateRenderTarget(IPlatformHandle handle)
         {
             throw new NotImplementedException();
         }

+ 11 - 1
tests/Avalonia.UnitTests/TestRoot.cs

@@ -41,7 +41,17 @@ namespace Avalonia.UnitTests
 
         public IRenderTarget RenderTarget => null;
 
-        public IRenderQueueManager RenderQueueManager => null;
+        public IRenderer Renderer => null;
+
+        public IRenderTarget CreateRenderTarget()
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Invalidate(Rect rect)
+        {
+            throw new NotImplementedException();
+        }
 
         public Point PointToClient(Point p) => p;
 

+ 14 - 2
tests/Avalonia.UnitTests/TestServices.cs

@@ -11,6 +11,7 @@ using Avalonia.Platform;
 using Avalonia.Shared.PlatformSupport;
 using Avalonia.Styling;
 using Avalonia.Themes.Default;
+using Avalonia.Rendering;
 
 namespace Avalonia.UnitTests
 {
@@ -20,7 +21,9 @@ namespace Avalonia.UnitTests
             assetLoader: new AssetLoader(),
             layoutManager: new LayoutManager(),
             platform: new AppBuilder().RuntimePlatform,
+            renderer: Mock.Of<IRenderer>(),
             renderInterface: CreateRenderInterfaceMock(),
+            renderLoop: Mock.Of<IRenderLoop>(),
             standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
             styler: new Styler(),
             theme: () => CreateDefaultTheme(),
@@ -57,7 +60,9 @@ namespace Avalonia.UnitTests
             Func<IKeyboardDevice> keyboardDevice = null,
             ILayoutManager layoutManager = null,
             IRuntimePlatform platform = null,
+            IRenderer renderer = null,
             IPlatformRenderInterface renderInterface = null,
+            IRenderLoop renderLoop = null,
             IStandardCursorFactory standardCursorFactory = null,
             IStyler styler = null,
             Func<Styles> theme = null,
@@ -71,7 +76,9 @@ namespace Avalonia.UnitTests
             KeyboardDevice = keyboardDevice;
             LayoutManager = layoutManager;
             Platform = platform;
+            Renderer = renderer;
             RenderInterface = renderInterface;
+            RenderLoop = renderLoop;
             StandardCursorFactory = standardCursorFactory;
             Styler = styler;
             Theme = theme;
@@ -86,7 +93,9 @@ namespace Avalonia.UnitTests
         public Func<IKeyboardDevice> KeyboardDevice { get; }
         public ILayoutManager LayoutManager { get; }
         public IRuntimePlatform Platform { get; }
+        public IRenderer Renderer { get; }
         public IPlatformRenderInterface RenderInterface { get; }
+        public IRenderLoop RenderLoop { get; }
         public IStandardCursorFactory StandardCursorFactory { get; }
         public IStyler Styler { get; }
         public Func<Styles> Theme { get; }
@@ -101,7 +110,9 @@ namespace Avalonia.UnitTests
             Func<IKeyboardDevice> keyboardDevice = null,
             ILayoutManager layoutManager = null,
             IRuntimePlatform platform = null,
+            IRenderer renderer = null,
             IPlatformRenderInterface renderInterface = null,
+            IRenderLoop renderLoop = null,
             IStandardCursorFactory standardCursorFactory = null,
             IStyler styler = null,
             Func<Styles> theme = null,
@@ -116,7 +127,9 @@ namespace Avalonia.UnitTests
                 keyboardDevice: keyboardDevice ?? KeyboardDevice,
                 layoutManager: layoutManager ?? LayoutManager,
                 platform: platform ?? Platform,
+                renderer: renderer ?? Renderer,
                 renderInterface: renderInterface ?? RenderInterface,
+                renderLoop: renderLoop ?? RenderLoop,
                 standardCursorFactory: standardCursorFactory ?? StandardCursorFactory,
                 styler: styler ?? Styler,
                 theme: theme ?? Theme,
@@ -152,8 +165,7 @@ namespace Avalonia.UnitTests
                     It.IsAny<FontWeight>(),
                     It.IsAny<TextWrapping>()) == Mock.Of<IFormattedTextImpl>() &&
                 x.CreateStreamGeometry() == Mock.Of<IStreamGeometryImpl>(
-                    y => y.Open() == Mock.Of<IStreamGeometryContextImpl>()) &&
-                x.CreateRenderer(It.IsAny<IPlatformHandle>()) == Mock.Of<IRenderTarget>());
+                    y => y.Open() == Mock.Of<IStreamGeometryContextImpl>()));
         }
     }
 }

+ 11 - 1
tests/Avalonia.UnitTests/TestTemplatedRoot.cs

@@ -43,7 +43,17 @@ namespace Avalonia.UnitTests
 
         public IRenderTarget RenderTarget => null;
 
-        public IRenderQueueManager RenderQueueManager => null;
+        public IRenderer Renderer => null;
+
+        public IRenderTarget CreateRenderTarget()
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Invalidate(Rect rect)
+        {
+            throw new NotImplementedException();
+        }
 
         public Point PointToClient(Point p) => p;
 

+ 3 - 0
tests/Avalonia.UnitTests/UnitTestApplication.cs

@@ -7,6 +7,7 @@ using Avalonia.Layout;
 using Avalonia.Platform;
 using Avalonia.Styling;
 using Avalonia.Controls;
+using Avalonia.Rendering;
 
 namespace Avalonia.UnitTests
 {
@@ -42,7 +43,9 @@ namespace Avalonia.UnitTests
                 .Bind<IKeyboardDevice>().ToConstant(Services.KeyboardDevice?.Invoke())
                 .Bind<ILayoutManager>().ToConstant(Services.LayoutManager)
                 .Bind<IRuntimePlatform>().ToConstant(Services.Platform)
+                .Bind<IRenderer>().ToConstant(Services.Renderer)
                 .Bind<IPlatformRenderInterface>().ToConstant(Services.RenderInterface)
+                .Bind<IRenderLoop>().ToConstant(Services.RenderLoop)
                 .Bind<IPlatformThreadingInterface>().ToConstant(Services.ThreadingInterface)
                 .Bind<IStandardCursorFactory>().ToConstant(Services.StandardCursorFactory)
                 .Bind<IStyler>().ToConstant(Services.Styler)