diff --git a/Avalonia.sln b/Avalonia.sln
index f65ca0b54e..7a0ae0ce53 100644
--- a/Avalonia.sln
+++ b/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
diff --git a/samples/RenderTest/App.config b/samples/RenderTest/App.config
new file mode 100644
index 0000000000..2231c71e63
--- /dev/null
+++ b/samples/RenderTest/App.config
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/RenderTest/App.xaml b/samples/RenderTest/App.xaml
new file mode 100644
index 0000000000..d9630eef58
--- /dev/null
+++ b/samples/RenderTest/App.xaml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/RenderTest/App.xaml.cs b/samples/RenderTest/App.xaml.cs
new file mode 100644
index 0000000000..4492e5ae4c
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/samples/RenderTest/MainWindow.xaml b/samples/RenderTest/MainWindow.xaml
new file mode 100644
index 0000000000..ab233fd90b
--- /dev/null
+++ b/samples/RenderTest/MainWindow.xaml
@@ -0,0 +1,3 @@
+
+
\ No newline at end of file
diff --git a/samples/RenderTest/MainWindow.xaml.cs b/samples/RenderTest/MainWindow.xaml.cs
new file mode 100644
index 0000000000..feb121186f
--- /dev/null
+++ b/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;
+ }
+ }
+}
diff --git a/samples/RenderTest/Program.cs b/samples/RenderTest/Program.cs
new file mode 100644
index 0000000000..7a23e09dd4
--- /dev/null
+++ b/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()
+ .UsePlatformDetect()
+ .Start();
+ }
+
+ // 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
+ }
+ }
+}
diff --git a/samples/RenderTest/Properties/AssemblyInfo.cs b/samples/RenderTest/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..f66c158075
--- /dev/null
+++ b/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")]
diff --git a/samples/RenderTest/RenderTest.csproj b/samples/RenderTest/RenderTest.csproj
new file mode 100644
index 0000000000..49ce4603ce
--- /dev/null
+++ b/samples/RenderTest/RenderTest.csproj
@@ -0,0 +1,192 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}
+ WinExe
+ Properties
+ RenderTest
+ RenderTest
+ v4.5
+ 512
+ true
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+ ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll
+ True
+
+
+ ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.FullNetFx.dll
+ True
+
+
+
+
+ ..\..\packages\System.Reactive.Core.3.0.0\lib\net45\System.Reactive.Core.dll
+ True
+
+
+ ..\..\packages\System.Reactive.Interfaces.3.0.0\lib\net45\System.Reactive.Interfaces.dll
+ True
+
+
+ ..\..\packages\System.Reactive.Linq.3.0.0\lib\net45\System.Reactive.Linq.dll
+ True
+
+
+ ..\..\packages\System.Reactive.PlatformServices.3.0.0\lib\net45\System.Reactive.PlatformServices.dll
+ True
+
+
+ ..\..\packages\System.Reactive.Windows.Threading.3.0.0\lib\net45\System.Reactive.Windows.Threading.dll
+ True
+
+
+
+
+
+
+
+
+
+
+
+ App.xaml
+
+
+
+
+ MainWindow.xaml
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+ {d211e587-d8bc-45b9-95a4-f297c8fa5200}
+ Avalonia.Animation
+
+
+ {b09b78d8-9b26-48b0-9149-d64a2f120f3f}
+ Avalonia.Base
+
+
+ {d2221c82-4a25-4583-9b43-d791e3f6820c}
+ Avalonia.Controls
+
+
+ {799a7bb5-3c2c-48b6-85a7-406a12c420da}
+ Avalonia.DesignerSupport
+
+
+ {7062ae20-5dcc-4442-9645-8195bdece63e}
+ Avalonia.Diagnostics
+
+
+ {4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}
+ Avalonia.DotNetFrameworkRuntime
+
+
+ {62024b2d-53eb-4638-b26b-85eeaa54866e}
+ Avalonia.Input
+
+
+ {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}
+ Avalonia.Interactivity
+
+
+ {42472427-4774-4c81-8aff-9f27b8e31721}
+ Avalonia.Layout
+
+
+ {b61b66a3-b82d-4875-8001-89d3394fe0c9}
+ Avalonia.Logging.Serilog
+
+
+ {6417b24e-49c2-4985-8db2-3ab9d898ec91}
+ Avalonia.ReactiveUI
+
+
+ {eb582467-6abb-43a1-b052-e981ba910e3a}
+ Avalonia.SceneGraph
+
+
+ {f1baa01a-f176-4c6a-b39d-5b40bb1b148f}
+ Avalonia.Styling
+
+
+ {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}
+ Avalonia.Themes.Default
+
+
+ {fb05ac90-89ba-4f2f-a924-f37875fb547c}
+ Avalonia.Cairo
+
+
+ {54f237d5-a70a-4752-9656-0c70b1a7b047}
+ Avalonia.Gtk
+
+
+ {3e53a01a-b331-47f3-b828-4a5717e77a24}
+ Avalonia.Markup.Xaml
+
+
+ {6417e941-21bc-467b-a771-0de389353ce6}
+ Avalonia.Markup
+
+
+ {3e908f67-5543-4879-a1dc-08eace79b3cd}
+ Avalonia.Direct2D1
+
+
+ {811a76cf-1cf6-440f-963b-bbe31bd72a82}
+ Avalonia.Win32
+
+
+
+
+ Designer
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/RenderTest/RenderTest.v2.ncrunchproject b/samples/RenderTest/RenderTest.v2.ncrunchproject
new file mode 100644
index 0000000000..30815b1937
--- /dev/null
+++ b/samples/RenderTest/RenderTest.v2.ncrunchproject
@@ -0,0 +1,26 @@
+
+ true
+ 1000
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ false
+ true
+ true
+ true
+ 60000
+
+
+
+ AutoDetect
+ STA
+ x86
+
\ No newline at end of file
diff --git a/samples/RenderTest/packages.config b/samples/RenderTest/packages.config
new file mode 100644
index 0000000000..3c79dde8c3
--- /dev/null
+++ b/samples/RenderTest/packages.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index 673cd9b040..c118bbd9a9 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/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().ToConstant(Instance)
.Bind().ToConstant(new AndroidThreadingInterface())
.Bind().ToTransient()
- .Bind().ToTransient()
.Bind().ToConstant(Instance);
+
+ SkiaPlatform.Initialize();
}
public void Init(Type applicationType)
diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj
index 9af96f0f93..a412930b85 100644
--- a/src/Android/Avalonia.Android/Avalonia.Android.csproj
+++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj
@@ -74,7 +74,6 @@
-
diff --git a/src/Android/Avalonia.Android/Platform/AndroidTopLevelRenderer.cs b/src/Android/Avalonia.Android/Platform/AndroidTopLevelRenderer.cs
deleted file mode 100644
index 0807471fe9..0000000000
--- a/src/Android/Avalonia.Android/Platform/AndroidTopLevelRenderer.cs
+++ /dev/null
@@ -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();
- 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();
- };
- }
- }
-}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
index a63ccec7c2..8101e4b550 100644
--- a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
@@ -26,6 +26,5 @@ namespace Avalonia.Platform
bool CurrentThreadIsLoopThread { get; }
event Action Signaled;
-
}
}
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 1991f49ac0..326556f629 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/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().ToTransient()
.Bind().ToConstant(_styler)
.Bind().ToSingleton()
- .Bind().ToTransient()
.Bind().ToConstant(this);
}
}
diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj
index 3439ab3101..3d27a5e501 100644
--- a/src/Avalonia.Controls/Avalonia.Controls.csproj
+++ b/src/Avalonia.Controls/Avalonia.Controls.csproj
@@ -73,7 +73,6 @@
-
diff --git a/src/Avalonia.Controls/Platform/ITopLevelRenderer.cs b/src/Avalonia.Controls/Platform/ITopLevelRenderer.cs
deleted file mode 100644
index 1653ff0a6d..0000000000
--- a/src/Avalonia.Controls/Platform/ITopLevelRenderer.cs
+++ /dev/null
@@ -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();
- 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();
- };
-
- }
- }
-}
diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs
index 2125505efd..8f069a3ab2 100644
--- a/src/Avalonia.Controls/Platform/PlatformManager.cs
+++ b/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()
- .CreateRenderer(window.Handle);
- }
-
public static IDisposable DesignerMode()
{
s_designerMode = true;
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index f64fb3cbd2..43c59f0b31 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -45,11 +45,11 @@ namespace Avalonia.Controls
public static readonly StyledProperty PointerOverElementProperty =
AvaloniaProperty.Register(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(dependencyResolver);
+
_accessKeyHandler = TryGetService(dependencyResolver);
_inputManager = TryGetService(dependencyResolver);
_keyboardNavigationHandler = TryGetService(dependencyResolver);
- _renderQueueManager = TryGetService(dependencyResolver);
_applicationLifecycle = TryGetService(dependencyResolver);
+ _renderInterface = TryGetService(dependencyResolver);
- (dependencyResolver.GetService() ?? new DefaultTopLevelRenderer()).Attach(this);
+ var renderLoop = TryGetService(dependencyResolver);
+ var rendererFactory = TryGetService(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)Renderer.Render : null;
PlatformImpl.Resized = HandleResized;
PlatformImpl.ScalingChanged = HandleScalingChanged;
PlatformImpl.PositionChanged = HandlePositionChanged;
@@ -179,9 +182,9 @@ namespace Avalonia.Controls
}
///
- /// Gets the window render manager.
+ /// Gets the renderer for the window.
///
- IRenderQueueManager IRenderRoot.RenderQueueManager => _renderQueueManager;
+ public IRenderer Renderer { get; }
///
/// Gets the access key handler for the window.
@@ -231,6 +234,18 @@ namespace Avalonia.Controls
private set;
}
+ ///
+ IRenderTarget IRenderRoot.CreateRenderTarget()
+ {
+ return _renderInterface.CreateRenderTarget(PlatformImpl.Handle);
+ }
+
+ ///
+ void IRenderRoot.Invalidate(Rect rect)
+ {
+ PlatformImpl.Invalidate(rect);
+ }
+
///
Point IRenderRoot.PointToClient(Point p)
{
@@ -321,8 +336,8 @@ namespace Avalonia.Controls
}
///
- /// Tries to get a service from an , throwing an
- /// exception if not found.
+ /// Tries to get a service from an , logging a
+ /// warning if not found.
///
/// The service type.
/// The resolver.
diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs
index 9d3a5b6536..745de95bca 100644
--- a/src/Avalonia.Controls/WrapPanel.cs
+++ b/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.
///
- internal class WrapPanel : Panel, INavigableContainer
+ public class WrapPanel : Panel, INavigableContainer
{
///
/// Defines the property.
diff --git a/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj b/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj
index f472c18230..3d2347683d 100644
--- a/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj
+++ b/src/Avalonia.SceneGraph/Avalonia.SceneGraph.csproj
@@ -102,6 +102,12 @@
+
+
+
+
+
+
@@ -139,9 +145,6 @@
-
-
-
diff --git a/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs b/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs
index 6beb787aab..c129cbd905 100644
--- a/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.SceneGraph/Platform/IPlatformRenderInterface.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Platform
///
/// The platform handle for the renderer.
/// An .
- IRenderTarget CreateRenderer(IPlatformHandle handle);
+ IRenderTarget CreateRenderTarget(IPlatformHandle handle);
///
/// Creates a render target bitmap implementation.
diff --git a/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs b/src/Avalonia.SceneGraph/Rendering/DefaultRenderLoop.cs
new file mode 100644
index 0000000000..a4e8e6f0c2
--- /dev/null
+++ b/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
+{
+ ///
+ /// Defines a default render loop that uses a standard timer.
+ ///
+ ///
+ /// This class may be overridden by platform implementations to use a specialized timer
+ /// implementation.
+ ///
+ public class DefaultRenderLoop : IRenderLoop
+ {
+ private IPlatformThreadingInterface _threading;
+ private int _subscriberCount;
+ private EventHandler _tick;
+ private IDisposable _subscription;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The number of frames per second at which the loop should run.
+ ///
+ public DefaultRenderLoop(int framesPerSecond)
+ {
+ FramesPerSecond = framesPerSecond;
+ }
+
+ ///
+ /// Gets the number of frames per second at which the loop runs.
+ ///
+ public int FramesPerSecond { get; }
+
+ ///
+ public event EventHandler Tick
+ {
+ add
+ {
+ if (_subscriberCount++ == 0)
+ {
+ Start();
+ }
+
+ _tick += value;
+ }
+
+ remove
+ {
+ if (--_subscriberCount == 0)
+ {
+ Stop();
+ }
+
+ _tick -= value;
+ }
+ }
+
+ ///
+ /// Starts the timer.
+ ///
+ protected void Start()
+ {
+ _subscription = StartCore(InternalTick);
+ }
+
+ ///
+ /// Provides the implementation of starting the timer.
+ ///
+ /// The method to call on each tick.
+ ///
+ /// This can be overridden by platform implementations to use a specialized timer
+ /// implementation.
+ ///
+ protected virtual IDisposable StartCore(Action tick)
+ {
+ if (_threading == null)
+ {
+ _threading = AvaloniaLocator.Current.GetService();
+ }
+
+ return _threading.StartTimer(TimeSpan.FromSeconds(1.0 / FramesPerSecond), tick);
+ }
+
+ ///
+ /// Stops the timer.
+ ///
+ protected void Stop()
+ {
+ _subscription.Dispose();
+ _subscription = null;
+ }
+
+ private void InternalTick()
+ {
+ _tick(this, EventArgs.Empty);
+ }
+ }
+}
diff --git a/src/Avalonia.SceneGraph/Rendering/IRenderLoop.cs b/src/Avalonia.SceneGraph/Rendering/IRenderLoop.cs
new file mode 100644
index 0000000000..36d915ddbd
--- /dev/null
+++ b/src/Avalonia.SceneGraph/Rendering/IRenderLoop.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Avalonia.Rendering
+{
+ ///
+ /// Defines the interface implemented by an application render loop.
+ ///
+ public interface IRenderLoop
+ {
+ ///
+ /// Raised when the render loop ticks to signal a new frame should be drawn.
+ ///
+ ///
+ /// This event can be raised on any thread; it is the responsibility of the subscriber to
+ /// switch execution to the right thread.
+ ///
+ event EventHandler Tick;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.SceneGraph/Rendering/IRenderQueueManager.cs b/src/Avalonia.SceneGraph/Rendering/IRenderQueueManager.cs
deleted file mode 100644
index 580e8b3046..0000000000
--- a/src/Avalonia.SceneGraph/Rendering/IRenderQueueManager.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Defines the interface for a .
- ///
- public interface IRenderQueueManager
- {
- ///
- /// Gets an observable that is fired whenever a render is required.
- ///
- IObservable RenderNeeded { get; }
-
- ///
- /// Invalidates the render for the specified visual and raises .
- ///
- /// The visual.
- void InvalidateRender(IVisual visual);
-
- ///
- /// Called when rendering is finished.
- ///
- void RenderFinished();
- }
-}
diff --git a/src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs b/src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs
index 254cb5bc1b..50cda1b1b8 100644
--- a/src/Avalonia.SceneGraph/Rendering/IRenderRoot.cs
+++ b/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
{
///
/// Represents the root of a renderable tree.
///
- public interface IRenderRoot
+ public interface IRenderRoot : IVisual
{
///
- /// Gets the render manager which schedules renders.
+ /// Gets the client size of the window.
///
- IRenderQueueManager RenderQueueManager { get; }
+ Size ClientSize { get; }
+
+ ///
+ /// Gets the renderer for the window.
+ ///
+ IRenderer Renderer { get; }
+
+ ///
+ /// Creates a render target for the window.
+ ///
+ /// An .
+ IRenderTarget CreateRenderTarget();
+
+ ///
+ /// Adds a rectangle to the window's dirty region.
+ ///
+ /// The rectangle.
+ void Invalidate(Rect rect);
///
/// Converts a point from screen to client coordinates.
diff --git a/src/Avalonia.SceneGraph/Rendering/IRenderer.cs b/src/Avalonia.SceneGraph/Rendering/IRenderer.cs
new file mode 100644
index 0000000000..c643662179
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.SceneGraph/Rendering/IRendererFactory.cs b/src/Avalonia.SceneGraph/Rendering/IRendererFactory.cs
new file mode 100644
index 0000000000..8e27f85193
--- /dev/null
+++ b/src/Avalonia.SceneGraph/Rendering/IRendererFactory.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Avalonia.Rendering
+{
+ ///
+ /// Defines a factory for creating instances.
+ ///
+ public interface IRendererFactory
+ {
+ ///
+ /// Creates a new renderer for the specified render root.
+ ///
+ /// The render root.
+ /// The render loop.
+ /// An instance of an .
+ IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop);
+ }
+}
diff --git a/src/Avalonia.SceneGraph/Rendering/RenderQueueManager.cs b/src/Avalonia.SceneGraph/Rendering/RenderQueueManager.cs
deleted file mode 100644
index 8b64788c16..0000000000
--- a/src/Avalonia.SceneGraph/Rendering/RenderQueueManager.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Schedules the rendering of a tree.
- ///
- public class RenderQueueManager : IRenderQueueManager
- {
- private readonly Subject _renderNeeded = new Subject();
-
- private bool _renderQueued;
-
- ///
- /// Gets an observable that is fired whenever a render is required.
- ///
- public IObservable RenderNeeded => _renderNeeded;
-
- ///
- /// Gets a value indicating whether a render is queued.
- ///
- public bool RenderQueued => _renderQueued;
-
- ///
- /// Invalidates the render for the specified visual and raises .
- ///
- /// The visual.
- public void InvalidateRender(IVisual visual)
- {
- if (!_renderQueued)
- {
- _renderQueued = true;
- _renderNeeded.OnNext(Unit.Default);
- }
- }
-
- ///
- /// Called when rendering is finished.
- ///
- public void RenderFinished()
- {
- _renderQueued = false;
- }
- }
-}
diff --git a/src/Avalonia.SceneGraph/Rendering/Renderer.cs b/src/Avalonia.SceneGraph/Rendering/Renderer.cs
new file mode 100644
index 0000000000..ed58d129af
--- /dev/null
+++ b/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(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));
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs
index f5dc3c4566..3d13501edd 100644
--- a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs
+++ b/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;
diff --git a/src/Avalonia.SceneGraph/Visual.cs b/src/Avalonia.SceneGraph/Visual.cs
index 54786ac85a..dbc678eee5 100644
--- a/src/Avalonia.SceneGraph/Visual.cs
+++ b/src/Avalonia.SceneGraph/Visual.cs
@@ -259,7 +259,7 @@ namespace Avalonia
///
public void InvalidateVisual()
{
- VisualRoot?.RenderQueueManager?.InvalidateRender(this);
+ VisualRoot?.Renderer?.AddDirty(this);
}
///
diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs
index 1e6eef103b..e59bcfffa1 100644
--- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs
+++ b/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)
diff --git a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs
index e79c51915d..38bada2f25 100644
--- a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs
+++ b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs
@@ -25,9 +25,10 @@ namespace Avalonia
namespace Avalonia.Gtk
{
using System.IO;
+ using Rendering;
using Gtk = global::Gtk;
- public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader
+ public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IRendererFactory
{
private static readonly GtkPlatform s_instance = new GtkPlatform();
private static Thread _uiThread;
@@ -53,6 +54,8 @@ namespace Avalonia.Gtk
.Bind().ToConstant(GtkMouseDevice.Instance)
.Bind().ToConstant(s_instance)
.Bind().ToConstant(s_instance)
+ .Bind().ToConstant(s_instance)
+ .Bind().ToConstant(new DefaultRenderLoop(60))
.Bind().ToSingleton()
.Bind().ToConstant(s_instance);
_uiThread = Thread.CurrentThread;
@@ -110,6 +113,11 @@ namespace Avalonia.Gtk
return new PopupImpl();
}
+ public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
+ {
+ return new Renderer(root, renderLoop);
+ }
+
public IWindowIconImpl LoadIcon(string fileName)
{
return new IconImpl(new Gdk.Pixbuf(fileName));
diff --git a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs
index 5282a4a3e0..62d8e7bd18 100644
--- a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs
+++ b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Skia.Android
{
_renderTarget =
AvaloniaLocator.Current.GetService()
- .CreateRenderer(this);
+ .CreateRenderTarget(this);
}
protected override void Draw()
diff --git a/src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs b/src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs
index 6faba3d8dc..c27f54b277 100644
--- a/src/Skia/Avalonia.Skia.iOS.TestApp/MainView.cs
+++ b/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()
- .CreateRenderer(AvaloniaPlatformHandle);
+ .CreateRenderTarget(AvaloniaPlatformHandle);
UpdateText(0);
}
double _radians = 0;
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index 86f884b592..f37fd7c1c6 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/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);
}
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 7222976a33..b43eef2fa9 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/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().ToConstant(s_instance)
+ .Bind().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")
{
diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
index c4bb5c8a25..51ad5ce05e 100644
--- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
+++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
@@ -70,6 +70,7 @@
+
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index ae030df49a..d115b35a23 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/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,
diff --git a/src/Windows/Avalonia.Win32/RenderLoop.cs b/src/Windows/Avalonia.Win32/RenderLoop.cs
new file mode 100644
index 0000000000..7d7befcc33
--- /dev/null
+++ b/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);
+ });
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 4aa1346a98..b437100cee 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/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().ToConstant(WindowsMouseDevice.Instance)
.Bind().ToConstant(s_instance)
.Bind().ToConstant(s_instance)
+ .Bind().ToConstant(new RenderLoop(60))
.Bind().ToSingleton()
.Bind().ToConstant(s_instance)
.Bind().ToConstant(s_instance);
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 872a7cf36b..65e10cafda 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/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;
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
index 1433bc7f85..847662e629 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
+++ b/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)
{
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
index 8d10b01f94..e603925e31 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
+++ b/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)
{
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index 54338a773e..367070b37a 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/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();
- renderInterface.Setup(x => x.CreateRenderer(It.IsAny())).Returns(() => new Mock().Object);
AvaloniaLocator.CurrentMutable
.Bind().ToTransient()
diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
index f4cca0c684..e4e7551f5c 100644
--- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
+++ b/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();
}
diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
index b1f39673c4..b3e9b981a5 100644
--- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
+++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
@@ -137,7 +137,6 @@ namespace Avalonia.Layout.UnitTests
var formattedText = fixture.Create();
var globalStyles = new Mock();
var renderInterface = fixture.Create();
- var renderManager = fixture.Create();
var windowImpl = new Mock();
windowImpl.SetupProperty(x => x.ClientSize);
@@ -151,7 +150,6 @@ namespace Avalonia.Layout.UnitTests
.Bind().ToConstant(new LayoutManager())
.Bind().ToConstant(new AppBuilder().RuntimePlatform)
.Bind().ToConstant(renderInterface)
- .Bind().ToConstant(renderManager)
.Bind().ToConstant(new Styler())
.Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object));
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index 99a7bbbcd2..1e257a698c 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/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