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