Browse Source
Rather than repainting whenever, this adds a render loop which is called N times per second. IRenderQueueManager and ITopLevelRenderer have been removed, replaced by IRenderer.pull/773/head
25 changed files with 291 additions and 187 deletions
@ -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,0 +1,9 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
public interface IRenderLoop |
|||
{ |
|||
event EventHandler<EventArgs> Tick; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// 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 Attach(IRenderRoot root, IRenderLoop renderLoop); |
|||
|
|||
void Render(Rect rect); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// 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 |
|||
{ |
|||
public class RenderLoop : IRenderLoop |
|||
{ |
|||
private readonly int _fps; |
|||
private IPlatformThreadingInterface _threading; |
|||
private int _subscriberCount; |
|||
private EventHandler<EventArgs> _tick; |
|||
private IDisposable _subscription; |
|||
|
|||
public RenderLoop(int fps) |
|||
{ |
|||
_fps = fps; |
|||
} |
|||
|
|||
public event EventHandler<EventArgs> Tick |
|||
{ |
|||
add |
|||
{ |
|||
if (_subscriberCount++ == 0) |
|||
{ |
|||
Start(); |
|||
} |
|||
|
|||
_tick += value; |
|||
} |
|||
|
|||
remove |
|||
{ |
|||
if (--_subscriberCount == 0) |
|||
{ |
|||
Stop(); |
|||
} |
|||
|
|||
_tick -= value; |
|||
} |
|||
} |
|||
|
|||
protected void Start() |
|||
{ |
|||
if (_threading == null) |
|||
{ |
|||
_threading = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>(); |
|||
} |
|||
|
|||
_subscription = _threading.StartTimer(TimeSpan.FromSeconds(1.0 / _fps), InternalTick); |
|||
} |
|||
|
|||
protected void Stop() |
|||
{ |
|||
_subscription.Dispose(); |
|||
_subscription = null; |
|||
} |
|||
|
|||
private void InternalTick() |
|||
{ |
|||
_tick(this, EventArgs.Empty); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// 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 IRenderLoop _renderLoop; |
|||
private IRenderRoot _root; |
|||
private IRenderTarget _renderTarget; |
|||
private bool _dirty; |
|||
|
|||
public void Attach(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; |
|||
} |
|||
} |
|||
|
|||
private void OnRenderLoopTick(object sender, EventArgs e) |
|||
{ |
|||
if (_dirty) |
|||
{ |
|||
_root.Invalidate(new Rect(_root.ClientSize)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue