csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
6.2 KiB
211 lines
6.2 KiB
using System;
|
|
using System.Diagnostics;
|
|
using Avalonia.Media;
|
|
using Avalonia.Platform;
|
|
using Avalonia.Rendering.SceneGraph;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Concurrent;
|
|
|
|
namespace Avalonia.Rendering
|
|
{
|
|
public class DeferredRenderer : IRenderer
|
|
{
|
|
private readonly IRenderLoop _renderLoop;
|
|
private readonly IRenderRoot _root;
|
|
private Scene _scene;
|
|
private IRenderTarget _renderTarget;
|
|
private List<IVisual> _dirty = new List<IVisual>();
|
|
private ConcurrentQueue<Rect> _renderQueue = new ConcurrentQueue<Rect>();
|
|
private bool _needsUpdate;
|
|
private bool _updateQueued;
|
|
private bool _needsRender;
|
|
|
|
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
|
|
private int _totalFrames;
|
|
private int _framesThisSecond;
|
|
private int _fps;
|
|
private TimeSpan _lastFpsUpdate;
|
|
|
|
public DeferredRenderer(IRenderRoot root, IRenderLoop renderLoop)
|
|
{
|
|
Contract.Requires<ArgumentNullException>(root != null);
|
|
|
|
_root = root;
|
|
_scene = new Scene(root);
|
|
_renderLoop = renderLoop;
|
|
_renderLoop.Tick += OnRenderLoopTick;
|
|
}
|
|
|
|
public bool DrawFps { get; set; }
|
|
|
|
public void AddDirty(IVisual visual)
|
|
{
|
|
// If the root of the scene has no children, then the scene is being set up; don't
|
|
// bother filling the dirty list with every control in the window.
|
|
if (_scene.Root.Children.Count > 0)
|
|
{
|
|
_dirty.Add(visual);
|
|
}
|
|
|
|
_needsUpdate = true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_renderLoop.Tick -= OnRenderLoopTick;
|
|
}
|
|
|
|
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
|
|
{
|
|
if (_needsUpdate)
|
|
{
|
|
UpdateScene();
|
|
}
|
|
|
|
return _scene.HitTest(p, filter);
|
|
}
|
|
|
|
public void Render(Rect rect)
|
|
{
|
|
}
|
|
|
|
private void Render(IDrawingContextImpl context, IVisualNode node, Rect clipBounds)
|
|
{
|
|
clipBounds = node.Bounds.Intersect(clipBounds);
|
|
|
|
if (!clipBounds.IsEmpty)
|
|
{
|
|
node.Render(context);
|
|
|
|
foreach (var child in node.Children)
|
|
{
|
|
var visualChild = child as IVisualNode;
|
|
|
|
if (visualChild != null)
|
|
{
|
|
Render(context, visualChild, clipBounds);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderFps(IDrawingContextImpl context)
|
|
{
|
|
var now = _stopwatch.Elapsed;
|
|
var elapsed = now - _lastFpsUpdate;
|
|
|
|
_framesThisSecond++;
|
|
|
|
if (elapsed.TotalSeconds > 1)
|
|
{
|
|
_fps = (int)(_framesThisSecond / elapsed.TotalSeconds);
|
|
_framesThisSecond = 0;
|
|
_lastFpsUpdate = now;
|
|
}
|
|
|
|
var pt = new Point(40, 40);
|
|
using (
|
|
var txt = new FormattedText("Frame #" + _totalFrames + " FPS: " + _fps, "Arial", 18,
|
|
FontStyle.Normal,
|
|
TextAlignment.Left,
|
|
FontWeight.Normal,
|
|
TextWrapping.NoWrap))
|
|
{
|
|
context.Transform = Matrix.Identity;
|
|
context.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
|
|
context.DrawText(Brushes.Black, pt, txt.PlatformImpl);
|
|
}
|
|
}
|
|
|
|
private void UpdateScene()
|
|
{
|
|
Dispatcher.UIThread.VerifyAccess();
|
|
|
|
try
|
|
{
|
|
var scene = _scene.Clone();
|
|
|
|
if (_dirty.Count > 0)
|
|
{
|
|
var dirtyRects = new DirtyRects();
|
|
|
|
foreach (var visual in _dirty)
|
|
{
|
|
SceneBuilder.Update(scene, visual, dirtyRects);
|
|
}
|
|
|
|
foreach (var r in dirtyRects.Coalesce())
|
|
{
|
|
_renderQueue.Enqueue(r);
|
|
}
|
|
|
|
_dirty.Clear();
|
|
}
|
|
else
|
|
{
|
|
SceneBuilder.UpdateAll(scene);
|
|
_renderQueue.Enqueue(new Rect(_root.ClientSize));
|
|
}
|
|
|
|
_scene = scene;
|
|
|
|
_needsUpdate = false;
|
|
_needsRender = true;
|
|
_root.Invalidate(new Rect(_root.ClientSize));
|
|
}
|
|
finally
|
|
{
|
|
_updateQueued = false;
|
|
}
|
|
}
|
|
|
|
private void OnRenderLoopTick(object sender, EventArgs e)
|
|
{
|
|
if (_needsUpdate && !_updateQueued)
|
|
{
|
|
Dispatcher.UIThread.InvokeAsync(UpdateScene, DispatcherPriority.Render);
|
|
_updateQueued = true;
|
|
}
|
|
|
|
if (_needsRender)
|
|
{
|
|
if (_renderTarget == null)
|
|
{
|
|
_renderTarget = _root.CreateRenderTarget();
|
|
}
|
|
|
|
try
|
|
{
|
|
_totalFrames++;
|
|
|
|
using (var context = _renderTarget.CreateDrawingContext())
|
|
{
|
|
Rect rect;
|
|
|
|
while (_renderQueue.TryDequeue(out rect))
|
|
{
|
|
context.PushClip(rect);
|
|
Render(context, _scene.Root, rect);
|
|
context.PopClip();
|
|
|
|
if (DrawFps)
|
|
{
|
|
RenderFps(context);
|
|
}
|
|
}
|
|
}
|
|
|
|
_needsRender = false;
|
|
}
|
|
catch (RenderTargetCorruptedException ex)
|
|
{
|
|
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
|
|
_renderTarget.Dispose();
|
|
_renderTarget = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|