Browse Source

Make centralized RenderLoop.

- Renamed `RenderLoop` to `RenderTimer`
- Added new `RenderLoop` which `DeferredRenderer`s register themselves with for updates
pull/1715/head
Steven Kirk 8 years ago
committed by Jeremy Koritzinsky
parent
commit
0b4e6b8471
  1. 2
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  2. 2
      src/Avalonia.Controls/TopLevel.cs
  3. 3
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  4. 1
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  5. 9
      src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs
  6. 44
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  7. 17
      src/Avalonia.Visuals/Rendering/IRenderLoop.cs
  8. 12
      src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs
  9. 20
      src/Avalonia.Visuals/Rendering/IRenderTimer.cs
  10. 111
      src/Avalonia.Visuals/Rendering/RenderLoop.cs
  11. 3
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  12. 3
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  13. 5
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  14. 4
      src/OSX/Avalonia.MonoMac/RenderTimer.cs
  15. 4
      src/Windows/Avalonia.Win32/RenderTimer.cs
  16. 3
      src/Windows/Avalonia.Win32/Win32Platform.cs
  17. 4
      tests/Avalonia.UnitTests/TestServices.cs
  18. 6
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

2
src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs

@ -9,7 +9,7 @@ using Avalonia.Threading;
namespace Avalonia.Controls.Platform
{
public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop
public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderTimer
{
public InternalPlatformThreadingInterface()
{

2
src/Avalonia.Controls/TopLevel.cs

@ -96,7 +96,7 @@ namespace Avalonia.Controls
_applicationLifecycle = TryGetService<IApplicationLifecycle>(dependencyResolver);
_renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
var renderLoop = TryGetService<IRenderLoop>(dependencyResolver);
var renderLoop = TryGetService<IRenderTimer>(dependencyResolver);
Renderer = impl.CreateRenderer(this);
impl.SetInputRoot(this);

3
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -53,7 +53,8 @@ namespace Avalonia.DesignerSupport.Remote
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformThreadingInterface>().ToConstant(threading)
.Bind<IRenderLoop>().ToConstant(threading)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(threading)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsStub>()
.Bind<IWindowingPlatform>().ToConstant(instance)
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>();

1
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />

9
src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs → src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs

@ -2,18 +2,19 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Rendering
{
/// <summary>
/// Defines a default render loop that uses a standard timer.
/// Defines a default render timer 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
public class DefaultRenderTimer : IRenderTimer
{
private IRuntimePlatform _runtime;
private int _subscriberCount;
@ -21,12 +22,12 @@ namespace Avalonia.Rendering
private IDisposable _subscription;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultRenderLoop"/> class.
/// Initializes a new instance of the <see cref="DefaultRenderTimer"/> class.
/// </summary>
/// <param name="framesPerSecond">
/// The number of frames per second at which the loop should run.
/// </param>
public DefaultRenderLoop(int framesPerSecond)
public DefaultRenderTimer(int framesPerSecond)
{
FramesPerSecond = framesPerSecond;
}

44
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -13,6 +13,7 @@ using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using System.Threading.Tasks;
namespace Avalonia.Rendering
{
@ -20,7 +21,7 @@ namespace Avalonia.Rendering
/// A renderer which renders the state of the visual tree to an intermediate scene graph
/// representation which is then rendered on a rendering thread.
/// </summary>
public class DeferredRenderer : RendererBase, IRenderer, IVisualBrushRenderer
public class DeferredRenderer : RendererBase, IRenderer, IRenderLoopTask, IVisualBrushRenderer
{
private readonly IDispatcher _dispatcher;
private readonly IRenderLoop _renderLoop;
@ -149,7 +150,7 @@ namespace Avalonia.Rendering
{
if (!_running && _renderLoop != null)
{
_renderLoop.Tick += OnRenderLoopTick;
_renderLoop.Add(this);
_running = true;
}
}
@ -159,11 +160,23 @@ namespace Avalonia.Rendering
{
if (_running && _renderLoop != null)
{
_renderLoop.Tick -= OnRenderLoopTick;
_renderLoop.Remove(this);
_running = false;
}
}
bool IRenderLoopTask.NeedsUpdate => _dirty == null || _dirty.Count > 0;
void IRenderLoopTask.Update() => UpdateScene();
void IRenderLoopTask.Render()
{
using (var scene = _scene?.Clone())
{
Render(scene?.Item);
}
}
/// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{
@ -420,31 +433,6 @@ namespace Avalonia.Rendering
}
}
private void OnRenderLoopTick(object sender, EventArgs e)
{
if (Monitor.TryEnter(_rendering))
{
try
{
if (!_updateQueued && (_dirty == null || _dirty.Count > 0))
{
_updateQueued = true;
_dispatcher.Post(UpdateScene, DispatcherPriority.Render);
}
using (var scene = _scene?.Clone())
{
Render(scene?.Item);
}
}
catch { }
finally
{
Monitor.Exit(_rendering);
}
}
}
private IRef<IRenderTargetBitmapImpl> GetOverlay(
IDrawingContextImpl parentContext,
Size size,

17
src/Avalonia.Visuals/Rendering/IRenderLoop.cs

@ -1,19 +1,8 @@
using System;
namespace Avalonia.Rendering
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;
void Add(IRenderLoopTask i);
void Remove(IRenderLoopTask i);
}
}

12
src/Avalonia.Visuals/Rendering/IRenderLoopTask.cs

@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;
namespace Avalonia.Rendering
{
public interface IRenderLoopTask
{
bool NeedsUpdate { get; }
void Update();
void Render();
}
}

20
src/Avalonia.Visuals/Rendering/IRenderTimer.cs

@ -0,0 +1,20 @@
using System;
using System.Threading.Tasks;
namespace Avalonia.Rendering
{
/// <summary>
/// Defines the interface implemented by an application render timer.
/// </summary>
public interface IRenderTimer
{
/// <summary>
/// Raised when the render timer 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;
}
}

111
src/Avalonia.Visuals/Rendering/RenderLoop.cs

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using Avalonia.Logging;
using Avalonia.Threading;
namespace Avalonia.Rendering
{
public class RenderLoop : IRenderLoop
{
private readonly IDispatcher _dispatcher;
private List<IRenderLoopTask> _items = new List<IRenderLoopTask>();
private IRenderTimer _timer;
private volatile bool inTick;
public RenderLoop()
{
_dispatcher = Dispatcher.UIThread;
}
public RenderLoop(IRenderTimer timer, IDispatcher dispatcher)
{
_timer = timer;
_dispatcher = dispatcher;
}
protected IRenderTimer Timer
{
get
{
if (_timer == null)
{
_timer = AvaloniaLocator.Current.GetService<IRenderTimer>();
}
return _timer;
}
}
public void Add(IRenderLoopTask i)
{
Contract.Requires<ArgumentNullException>(i != null);
Dispatcher.UIThread.VerifyAccess();
_items.Add(i);
if (_items.Count == 1)
{
Timer.Tick += TimerTick;
}
}
public void Remove(IRenderLoopTask i)
{
Contract.Requires<ArgumentNullException>(i != null);
Dispatcher.UIThread.VerifyAccess();
_items.Remove(i);
if (_items.Count == 0)
{
Timer.Tick -= TimerTick;
}
}
private async void TimerTick(object sender, EventArgs e)
{
if (!inTick)
{
inTick = true;
try
{
var needsUpdate = false;
foreach (var i in _items)
{
if (i.NeedsUpdate)
{
needsUpdate = true;
break;
}
}
if (needsUpdate)
{
await _dispatcher.InvokeAsync(() =>
{
foreach (var i in _items)
{
i.Update();
}
});
}
foreach (var i in _items)
{
i.Render();
}
}
catch (Exception ex)
{
Logger.Error(LogArea.Visual, this, "Exception in render loop: {Error}", ex);
}
finally
{
inTick = false;
}
}
}
}
}

3
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -52,7 +52,8 @@ namespace Avalonia.Gtk3
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IPlatformThreadingInterface>().ToConstant(Instance)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialog>()
.Bind<IRenderLoop>().ToConstant(new DefaultRenderLoop(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoader());
}

3
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -35,7 +35,8 @@ namespace Avalonia.LinuxFramebuffer
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(Threading)
.Bind<IRenderLoop>().ToConstant(Threading);
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(Threading);
}
internal static TopLevel Initialize<T>(T builder, string fbdev = null) where T : AppBuilderBase<T>, new()

5
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@ -21,6 +21,7 @@ namespace Avalonia.MonoMac
private static bool s_monoMacInitialized;
private static bool s_showInDock = true;
private static IRenderLoop s_renderLoop;
private static IRenderTimer s_renderTimer;
void DoInitialize()
{
@ -35,6 +36,7 @@ namespace Avalonia.MonoMac
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsImpl>()
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IRenderLoop>().ToConstant(s_renderLoop)
.Bind<IRenderTimer>().ToConstant(s_renderTimer)
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
/*.Bind<IPlatformDragSource>().ToTransient<DragSource>()*/;
}
@ -83,7 +85,8 @@ namespace Avalonia.MonoMac
ThreadHelper.InitializeCocoaThreadingLocks();
App = NSApplication.SharedApplication;
UpdateActivationPolicy();
s_renderLoop = new RenderLoop(); //TODO: use CVDisplayLink
s_renderLoop = new RenderLoop();
s_renderTimer = new RenderTimer(); //TODO: use CVDisplayLink
s_monoMacInitialized = true;
}

4
src/OSX/Avalonia.MonoMac/RenderLoop.cs → src/OSX/Avalonia.MonoMac/RenderTimer.cs

@ -6,12 +6,12 @@ using MonoMac.Foundation;
namespace Avalonia.MonoMac
{
//TODO: Switch to using CVDisplayLink
public class RenderLoop : IRenderLoop
public class RenderTimer : IRenderTimer
{
private readonly object _lock = new object();
private readonly IDisposable _timer;
public RenderLoop()
public RenderTimer()
{
_timer = AvaloniaLocator.Current.GetService<IRuntimePlatform>().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60),
() =>

4
src/Windows/Avalonia.Win32/RenderLoop.cs → src/Windows/Avalonia.Win32/RenderTimer.cs

@ -5,11 +5,11 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
internal class RenderLoop : DefaultRenderLoop
internal class RenderTimer : DefaultRenderTimer
{
private UnmanagedMethods.TimeCallback timerDelegate;
public RenderLoop(int framesPerSecond)
public RenderTimer(int framesPerSecond)
: base(framesPerSecond)
{
}

3
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -82,7 +82,8 @@ namespace Avalonia.Win32
.Bind<IKeyboardDevice>().ToConstant(WindowsKeyboardDevice.Instance)
.Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new RenderTimer(60))
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>()
.Bind<IWindowingPlatform>().ToConstant(s_instance)
.Bind<IPlatformIconLoader>().ToConstant(s_instance);

4
tests/Avalonia.UnitTests/TestServices.cs

@ -64,7 +64,7 @@ namespace Avalonia.UnitTests
Func<IMouseDevice> mouseDevice = null,
IRuntimePlatform platform = null,
IPlatformRenderInterface renderInterface = null,
IRenderLoop renderLoop = null,
IRenderTimer renderLoop = null,
IScheduler scheduler = null,
IStandardCursorFactory standardCursorFactory = null,
IStyler styler = null,
@ -115,7 +115,7 @@ namespace Avalonia.UnitTests
Func<IMouseDevice> mouseDevice = null,
IRuntimePlatform platform = null,
IPlatformRenderInterface renderInterface = null,
IRenderLoop renderLoop = null,
IRenderTimer renderLoop = null,
IScheduler scheduler = null,
IStandardCursorFactory standardCursorFactory = null,
IStyler styler = null,

6
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -42,7 +42,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
[Fact]
public void First_Frame_Calls_SceneBuilder_UpdateAll()
{
var loop = new Mock<IRenderLoop>();
var loop = new Mock<IRenderTimer>();
var root = new TestRoot();
var sceneBuilder = MockSceneBuilder(root);
@ -198,7 +198,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
[Fact]
public void Should_Create_Layer_For_Root()
{
var loop = new Mock<IRenderLoop>();
var loop = new Mock<IRenderTimer>();
var root = new TestRoot();
var rootLayer = new Mock<IRenderTargetBitmapImpl>();
@ -374,7 +374,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
private void RunFrame(Mock<IRenderLoop> loop)
{
loop.Raise(x => x.Tick += null, EventArgs.Empty);
//loop.Raise(x => x.Tick += null, EventArgs.Empty);
}
private IRenderTargetBitmapImpl CreateLayer()

Loading…
Cancel
Save