From 14805322ca8c4d9a38e3140c81edce29112d894a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 14 Dec 2016 21:57:10 +0100 Subject: [PATCH] Removed RendererMixin. Moved immediate rendering logic into `Renderer`. --- src/Avalonia.Visuals/Avalonia.Visuals.csproj | 1 - .../Media/Imaging/RenderTargetBitmap.cs | 7 + src/Avalonia.Visuals/Rendering/Renderer.cs | 140 +++++++++++- .../Rendering/RendererMixin.cs | 209 ------------------ .../RenderHelpers/TileBrushImplHelper.cs | 2 +- .../RenderTests_Culling.cs | 2 +- .../VisualTree/BoundsTrackerTests.cs | 2 +- .../VisualExtensionsTests_GetVisualsAt.cs | 2 +- 8 files changed, 145 insertions(+), 220 deletions(-) delete mode 100644 src/Avalonia.Visuals/Rendering/RendererMixin.cs diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index 01b1da43e7..3779512811 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -116,7 +116,6 @@ - diff --git a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs index 315b1916c7..8d8d738bb8 100644 --- a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs +++ b/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(); } + /// + /// Renders a visual to the . + /// + /// The visual to render. + public void Render(IVisual visual) => Renderer.Render(visual, this); + /// /// Creates a platform-specific imlementation for a . /// diff --git a/src/Avalonia.Visuals/Rendering/Renderer.cs b/src/Avalonia.Visuals/Rendering/Renderer.cs index 12ed088bbc..e0f6d6fc5e 100644 --- a/src/Avalonia.Visuals/Rendering/Renderer.cs +++ b/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(root != null); + Contract.Requires(renderLoop != null); _root = root; _renderLoop = renderLoop; _renderLoop.Tick += OnRenderLoopTick; } + private Renderer(IVisual root) + { + Contract.Requires(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 HitTest(Point p, Func 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 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))); } } } diff --git a/src/Avalonia.Visuals/Rendering/RendererMixin.cs b/src/Avalonia.Visuals/Rendering/RendererMixin.cs deleted file mode 100644 index c922eb50e1..0000000000 --- a/src/Avalonia.Visuals/Rendering/RendererMixin.cs +++ /dev/null @@ -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 -{ - /// - /// Extension methods for rendering. - /// - /// - /// This class provides implements the platform-independent parts of . - /// - [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> s_listPool = new Stack>(); - private static readonly ZIndexComparer s_visualComparer = new ZIndexComparer(); - - /// - /// Gets or sets a value which determines whether an FPS counted will be drawn on each - /// rendered frame. - /// - public static bool DrawFpsCounter { get; set; } - - /// - /// Renders the specified visual. - /// - /// IRenderer instance - /// The visual to render. - 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); - } - } - } - - /// - /// Renders the specified visual. - /// - /// The visual to render. - /// The drawing context. - public static void Render(this DrawingContext context, IVisual visual) - { - context.Render(visual, visual.Bounds); - } - - /// - /// Renders the specified visual. - /// - /// The visual to render. - /// The drawing context. - /// - /// The current clip rect, in coordinates relative to . - /// - 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 lst) - { - lst.Clear(); - s_listPool.Push(lst); - } - - private static List GetSortedVisualList(IReadOnlyList source) - { - var lst = s_listPool.Count == 0 ? new List() : 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 - { - public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex); - } - } -} diff --git a/src/Shared/RenderHelpers/TileBrushImplHelper.cs b/src/Shared/RenderHelpers/TileBrushImplHelper.cs index de26b6ca36..8c9f6aa05e 100644 --- a/src/Shared/RenderHelpers/TileBrushImplHelper.cs +++ b/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); } } } diff --git a/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs b/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs index caa7011075..9577a1076b 100644 --- a/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs +++ b/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() diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs index d18a604f31..15eae18f5a 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs +++ b/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(); diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs index 704cbf72d1..f25dbbb681 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs +++ b/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()); - context.Render(container); + ////context.Render(container); var result = container.GetVisualsAt(new Point(100, 100)); Assert.Equal(new[] { path }, result);