Browse Source

Removed RendererMixin.

Moved immediate rendering logic into `Renderer`.
scenegraph-after-breakage
Steven Kirk 9 years ago
parent
commit
14805322ca
  1. 1
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  2. 7
      src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
  3. 140
      src/Avalonia.Visuals/Rendering/Renderer.cs
  4. 209
      src/Avalonia.Visuals/Rendering/RendererMixin.cs
  5. 2
      src/Shared/RenderHelpers/TileBrushImplHelper.cs
  6. 2
      tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs
  7. 2
      tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs
  8. 2
      tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs

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

@ -116,7 +116,6 @@
<Compile Include="Rendering\IRenderLoop.cs" />
<Compile Include="Rendering\DirtyRects.cs" />
<Compile Include="Rendering\Renderer.cs" />
<Compile Include="Rendering\RendererMixin.cs" />
<Compile Include="Rendering\DefaultRenderLoop.cs" />
<Compile Include="Rendering\IRenderLayerFactory.cs" />
<Compile Include="Rendering\RenderLayer.cs" />

7
src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs

@ -3,6 +3,7 @@
using System;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Media.Imaging
@ -37,6 +38,12 @@ namespace Avalonia.Media.Imaging
PlatformImpl.Dispose();
}
/// <summary>
/// Renders a visual to the <see cref="RenderTargetBitmap"/>.
/// </summary>
/// <param name="visual">The visual to render.</param>
public void Render(IVisual visual) => Renderer.Render(visual, this);
/// <summary>
/// Creates a platform-specific imlementation for a <see cref="RenderTargetBitmap"/>.
/// </summary>

140
src/Avalonia.Visuals/Rendering/Renderer.cs

@ -6,13 +6,15 @@ using Avalonia.Platform;
using Avalonia.VisualTree;
using System.Collections.Generic;
using Avalonia.Threading;
using Avalonia.Media;
using System.Linq;
namespace Avalonia.Rendering
{
public class Renderer : IDisposable, IRenderer
{
private readonly IRenderLoop _renderLoop;
private readonly IRenderRoot _root;
private readonly IVisual _root;
private IRenderTarget _renderTarget;
private bool _dirty;
private bool _renderQueued;
@ -20,15 +22,40 @@ namespace Avalonia.Rendering
public Renderer(IRenderRoot root, IRenderLoop renderLoop)
{
Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(renderLoop != null);
_root = root;
_renderLoop = renderLoop;
_renderLoop.Tick += OnRenderLoopTick;
}
private Renderer(IVisual root)
{
Contract.Requires<ArgumentNullException>(root != null);
_root = root;
}
public bool DrawFps { get; set; }
public bool DrawDirtyRects { get; set; }
public static void Render(IVisual visual, IRenderTarget target)
{
using (var renderer = new Renderer(visual))
using (var context = new DrawingContext(target.CreateDrawingContext()))
{
renderer.Render(context, visual, visual.Bounds);
}
}
public static void Render(IVisual visual, DrawingContext context)
{
using (var renderer = new Renderer(visual))
{
renderer.Render(context, visual, visual.Bounds);
}
}
public void AddDirty(IVisual visual)
{
_dirty = true;
@ -36,7 +63,10 @@ namespace Avalonia.Rendering
public void Dispose()
{
_renderLoop.Tick -= OnRenderLoopTick;
if (_renderLoop != null)
{
_renderLoop.Tick -= OnRenderLoopTick;
}
}
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
@ -48,13 +78,12 @@ namespace Avalonia.Rendering
{
if (_renderTarget == null)
{
_renderTarget = _root.CreateRenderTarget();
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
try
{
RendererMixin.DrawFpsCounter = DrawFps;
_renderTarget.Render(_root);
Render(_root);
}
catch (RenderTargetCorruptedException ex)
{
@ -69,6 +98,29 @@ namespace Avalonia.Rendering
}
}
private static void ClearTransformedBounds(IVisual visual)
{
foreach (var e in visual.GetSelfAndVisualDescendents())
{
BoundsTracker.SetTransformedBounds((Visual)visual, null);
}
}
private static Rect GetTransformedBounds(IVisual visual)
{
if (visual.RenderTransform == null)
{
return visual.Bounds;
}
else
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin);
var m = (-offset) * visual.RenderTransform.Value * (offset);
return visual.Bounds.TransformToAABB(m);
}
}
static IEnumerable<IVisual> HitTest(
IVisual visual,
Point p,
@ -98,12 +150,88 @@ namespace Avalonia.Rendering
}
}
private void Render(IVisual visual)
{
using (var context = new DrawingContext(_renderTarget.CreateDrawingContext()))
{
Render(context, visual, visual.Bounds);
}
}
private void Render(DrawingContext context, IVisual visual, Rect clipRect)
{
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var bounds = new Rect(visual.Bounds.Size);
if (visual.IsVisible && opacity > 0)
{
var m = Matrix.CreateTranslation(visual.Bounds.Position);
var renderTransform = Matrix.Identity;
if (visual.RenderTransform != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
m = renderTransform * m;
if (clipToBounds)
{
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size));
}
using (context.PushPostTransform(m))
using (context.PushOpacity(opacity))
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState))
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())
{
visual.Render(context);
#pragma warning disable 0618
var transformed =
new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
#pragma warning restore 0618
if (visual is Visual)
{
BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
}
foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
{
var childBounds = GetTransformedBounds(child);
if (!child.ClipToBounds || clipRect.Intersects(childBounds))
{
var childClipRect = clipRect.Translate(-childBounds.Position);
Render(context, child, childClipRect);
}
else
{
ClearTransformedBounds(child);
}
}
}
}
if (!visual.IsVisible)
{
ClearTransformedBounds(visual);
}
}
private void OnRenderLoopTick(object sender, EventArgs e)
{
if (_dirty && !_renderQueued)
{
_renderQueued = true;
Dispatcher.UIThread.InvokeAsync(() => Render(new Rect(_root.ClientSize)));
Dispatcher.UIThread.InvokeAsync(() => Render(new Rect(((IRenderRoot)_root).ClientSize)));
}
}
}

209
src/Avalonia.Visuals/Rendering/RendererMixin.cs

@ -1,209 +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.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
/// <summary>
/// Extension methods for rendering.
/// </summary>
/// <remarks>
/// This class provides implements the platform-independent parts of <see cref="IRenderTarget"/>.
/// </remarks>
[SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")]
[SuppressMessage("ReSharper", "ForCanBeConvertedToForeach")]
public static class RendererMixin
{
private static int s_frameNum;
private static int s_fps;
private static int s_currentFrames;
private static TimeSpan s_lastMeasure;
private static readonly Stopwatch s_stopwatch = Stopwatch.StartNew();
private static readonly Stack<List<IVisual>> s_listPool = new Stack<List<IVisual>>();
private static readonly ZIndexComparer s_visualComparer = new ZIndexComparer();
/// <summary>
/// Gets or sets a value which determines whether an FPS counted will be drawn on each
/// rendered frame.
/// </summary>
public static bool DrawFpsCounter { get; set; }
/// <summary>
/// Renders the specified visual.
/// </summary>
/// <param name="renderTarget">IRenderer instance</param>
/// <param name="visual">The visual to render.</param>
public static void Render(this IRenderTarget renderTarget, IVisual visual)
{
using (var ctx = new DrawingContext(renderTarget.CreateDrawingContext()))
{
ctx.Render(visual);
s_frameNum++;
if (DrawFpsCounter)
{
s_currentFrames++;
var now = s_stopwatch.Elapsed;
var elapsed = now - s_lastMeasure;
if (elapsed.TotalSeconds > 1)
{
s_fps = (int)(s_currentFrames / elapsed.TotalSeconds);
s_currentFrames = 0;
s_lastMeasure = now;
}
var pt = new Point(40, 40);
var txt = new FormattedText(
"Frame #" + s_frameNum + " FPS: " + s_fps,
"Arial",
18,
Size.Infinity,
FontStyle.Normal,
TextAlignment.Left,
FontWeight.Normal,
TextWrapping.NoWrap);
ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
ctx.DrawText(Brushes.Black, pt, txt);
}
}
}
/// <summary>
/// Renders the specified visual.
/// </summary>
/// <param name="visual">The visual to render.</param>
/// <param name="context">The drawing context.</param>
public static void Render(this DrawingContext context, IVisual visual)
{
context.Render(visual, visual.Bounds);
}
/// <summary>
/// Renders the specified visual.
/// </summary>
/// <param name="visual">The visual to render.</param>
/// <param name="context">The drawing context.</param>
/// <param name="clipRect">
/// The current clip rect, in coordinates relative to <paramref name="visual"/>.
/// </param>
private static void Render(this DrawingContext context, IVisual visual, Rect clipRect)
{
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var bounds = new Rect(visual.Bounds.Size);
if (visual.IsVisible && opacity > 0)
{
var m = Matrix.CreateTranslation(visual.Bounds.Position);
var renderTransform = Matrix.Identity;
if (visual.RenderTransform != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
m = renderTransform * m;
if (clipToBounds)
{
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size));
}
using (context.PushPostTransform(m))
using (context.PushOpacity(opacity))
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState))
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())
{
visual.Render(context);
#pragma warning disable 0618
var transformed =
new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
#pragma warning restore 0618
if (visual is Visual)
{
BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
}
var lst = GetSortedVisualList(visual.VisualChildren);
foreach (var child in lst)
{
var childBounds = GetTransformedBounds(child);
if (!child.ClipToBounds || clipRect.Intersects(childBounds))
{
var childClipRect = clipRect.Translate(-childBounds.Position);
context.Render(child, childClipRect);
}
else
{
ClearTransformedBounds(child);
}
}
ReturnListToPool(lst);
}
}
if (!visual.IsVisible)
{
ClearTransformedBounds(visual);
}
}
private static void ClearTransformedBounds(IVisual visual)
{
foreach (var e in visual.GetSelfAndVisualDescendents())
{
BoundsTracker.SetTransformedBounds((Visual)visual, null);
}
}
private static void ReturnListToPool(List<IVisual> lst)
{
lst.Clear();
s_listPool.Push(lst);
}
private static List<IVisual> GetSortedVisualList(IReadOnlyList<IVisual> source)
{
var lst = s_listPool.Count == 0 ? new List<IVisual>() : s_listPool.Pop();
for (var c = 0; c < source.Count; c++)
lst.Add(source[c]);
lst.Sort(s_visualComparer);
return lst;
}
private static Rect GetTransformedBounds(IVisual visual)
{
if (visual.RenderTransform == null)
{
return visual.Bounds;
}
else
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin);
var m = (-offset) * visual.RenderTransform.Value * (offset);
return visual.Bounds.TransformToAABB(m);
}
}
class ZIndexComparer : IComparer<IVisual>
{
public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex);
}
}
}

2
src/Shared/RenderHelpers/TileBrushImplHelper.cs

@ -104,7 +104,7 @@ namespace Avalonia.RenderHelpers
{
using (ctx.PushPostTransform(Matrix.CreateTranslation(-_visualBrush.Visual.Bounds.Position)))
{
ctx.Render(_visualBrush.Visual);
Renderer.Render(_visualBrush.Visual, ctx);
}
}
}

2
tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs

@ -162,7 +162,7 @@ namespace Avalonia.Visuals.UnitTests
var ctx = CreateDrawingContext();
control.Measure(Size.Infinity);
control.Arrange(new Rect(control.DesiredSize));
ctx.Render(control);
////ctx.Render(control);
}
private DrawingContext CreateDrawingContext()

2
tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs

@ -43,7 +43,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(0, 0, 100, 100));
context.Render(tree);
////context.Render(tree);
var track = target.Track(control);
var results = new List<TransformedBounds?>();

2
tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs

@ -441,7 +441,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
////context.Render(container);
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { path }, result);

Loading…
Cancel
Save