From 6d3ca92a4c588fcdefa25c5519ecb29df3d5ccd1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 10 Dec 2016 15:22:18 +0100 Subject: [PATCH] WIP: Getting resizing working. --- samples/ControlCatalog/MainWindow.xaml.cs | 2 +- .../Rendering/DeferredRenderer.cs | 68 ++++++++++++------- src/Avalonia.Visuals/Rendering/DirtyRects.cs | 4 +- src/Avalonia.Visuals/Rendering/RenderLayer.cs | 36 +++++++--- .../Rendering/RenderLayers.cs | 18 ++--- .../Rendering/SceneGraph/Scene.cs | 12 +++- .../Rendering/SceneGraph/SceneBuilder.cs | 37 ++++++++++ .../Media/Imaging/BitmapImpl.cs | 3 +- .../Avalonia.Direct2D1/RenderTarget.cs | 1 + .../TopLevelTests.cs | 23 +++++++ tests/Avalonia.UnitTests/TestRoot.cs | 2 +- .../Rendering/DeferredRendererTests.cs | 1 + .../Rendering/SceneGraph/SceneBuilderTests.cs | 45 ++++++++++++ 13 files changed, 204 insertions(+), 48 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 413794dfa2..c786900deb 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -10,7 +10,7 @@ namespace ControlCatalog { this.InitializeComponent(); this.AttachDevTools(); - Renderer.DrawDirtyRects = Renderer.DrawFps = true; + //Renderer.DrawDirtyRects = Renderer.DrawFps = true; } private void InitializeComponent() diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 284dbde00e..e29eab0d41 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -87,6 +87,28 @@ namespace Avalonia.Rendering { } + private void Render(Scene scene) + { + _rendering = true; + _totalFrames++; + _dirtyRectsDisplay.Tick(); + + if (scene.Size != Size.Empty) + { + if (scene.Id != _lastSceneId) + { + _layers.RemoveUnused(scene); + RenderToLayers(scene); + _lastSceneId = scene.Id; + } + + RenderOverlay(scene); + RenderComposite(scene); + } + + _rendering = false; + } + private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) { if (node.LayerRoot == layer) @@ -118,7 +140,7 @@ namespace Avalonia.Rendering { foreach (var layer in scene.Layers) { - var renderTarget = GetRenderTargetForLayer(layer.LayerRoot); + var renderTarget = GetRenderTargetForLayer(scene, layer.LayerRoot); var node = (VisualNode)scene.FindNode(layer.LayerRoot); using (var context = renderTarget.CreateDrawingContext()) @@ -140,11 +162,11 @@ namespace Avalonia.Rendering } } - private void RenderOverlay() + private void RenderOverlay(Scene scene) { if (DrawFps || DrawDirtyRects) { - var overlay = GetOverlay(_root.ClientSize); + var overlay = GetOverlay(scene.Size); using (var context = overlay.CreateDrawingContext()) { @@ -214,11 +236,11 @@ namespace Avalonia.Rendering using (var context = _renderTarget.CreateDrawingContext()) { - var clientRect = new Rect(_root.ClientSize); + var clientRect = new Rect(scene.Size); foreach (var layer in scene.Layers) { - var renderLayer = _layers.Get(layer.LayerRoot); + var renderLayer = _layers[layer.LayerRoot]; context.DrawImage(renderLayer.Bitmap, layer.Opacity, clientRect, clientRect); } @@ -263,7 +285,7 @@ namespace Avalonia.Rendering } _dirty.Clear(); - _root.Invalidate(new Rect(_root.ClientSize)); + _root.Invalidate(new Rect(scene.Size)); } finally { @@ -284,10 +306,6 @@ namespace Avalonia.Rendering _dispatcher.InvokeAsync(UpdateScene, DispatcherPriority.Render); } - _rendering = true; - _totalFrames++; - _dirtyRectsDisplay.Tick(); - Scene scene; lock (_scene) @@ -295,17 +313,7 @@ namespace Avalonia.Rendering scene = _scene; } - if (scene.Id != _lastSceneId) - { - _layers.RemoveUnused(scene); - RenderToLayers(scene); - _lastSceneId = scene.Id; - } - - RenderOverlay(); - RenderComposite(scene); - - _rendering = false; + Render(scene); } private IRenderTargetBitmapImpl GetOverlay(Size size) @@ -322,9 +330,23 @@ namespace Avalonia.Rendering return _overlay; } - private IRenderTargetBitmapImpl GetRenderTargetForLayer(IVisual layerRoot) + private IRenderTargetBitmapImpl GetRenderTargetForLayer(Scene scene, IVisual layerRoot) { - return (_layers.Get(layerRoot) ?? _layers.Add(layerRoot, _root.ClientSize)).Bitmap; + RenderLayer result; + + if (_layers.TryGetValue(layerRoot, out result)) + { + if (result.Size != scene.Size) + { + result.ResizeBitmap(scene.Size); + } + } + else + { + _layers.Add(layerRoot, scene.Size); + } + + return _layers[layerRoot].Bitmap; } } } diff --git a/src/Avalonia.Visuals/Rendering/DirtyRects.cs b/src/Avalonia.Visuals/Rendering/DirtyRects.cs index 76cf8c8e5f..cbfe164202 100644 --- a/src/Avalonia.Visuals/Rendering/DirtyRects.cs +++ b/src/Avalonia.Visuals/Rendering/DirtyRects.cs @@ -21,7 +21,7 @@ namespace Avalonia.Rendering { var r = _rects[i]; - if (r.Inflate(1).Intersects(rect)) + if (r.Intersects(rect)) { _rects[i] = r.Union(rect); return; @@ -36,7 +36,7 @@ namespace Avalonia.Rendering { for (var i = _rects.Count - 1; i >= 0; --i) { - var a = _rects[i].Inflate(1); + var a = _rects[i]; for (var j = 0; j < i; ++j) { diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index cc6adf7f66..87624864d4 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs @@ -1,27 +1,48 @@ using System; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.VisualTree; namespace Avalonia.Rendering { - public class RenderLayer : IComparable + public class RenderLayer { + private readonly IRenderLayerFactory _factory; + public RenderLayer( - IRenderTargetBitmapImpl bitmap, + IRenderLayerFactory factory, Size size, IVisual layerRoot) { - Bitmap = bitmap; + _factory = factory; + Bitmap = factory.CreateLayer(layerRoot, size); Size = size; LayerRoot = layerRoot; Order = GetDistanceFromRenderRoot(layerRoot); } - public IRenderTargetBitmapImpl Bitmap { get; } - public Size Size { get; } + public IRenderTargetBitmapImpl Bitmap { get; private set; } + public Size Size { get; private set; } public IVisual LayerRoot { get; } public int Order { get; } + public void ResizeBitmap(Size size) + { + if (Size != size) + { + var resized = _factory.CreateLayer(LayerRoot, size); + + using (var context = resized.CreateDrawingContext()) + { + context.Clear(Colors.Black); + context.DrawImage(Bitmap, 1, new Rect(Size), new Rect(Size)); + Bitmap.Dispose(); + Bitmap = resized; + Size = size; + } + } + } + private static int GetDistanceFromRenderRoot(IVisual visual) { var root = visual as IRenderRoot; @@ -42,10 +63,5 @@ namespace Avalonia.Rendering return result; } - - public int CompareTo(RenderLayer other) - { - return Order - other.Order; - } } } diff --git a/src/Avalonia.Visuals/Rendering/RenderLayers.cs b/src/Avalonia.Visuals/Rendering/RenderLayers.cs index d7cd137496..e406990cfb 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayers.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayers.cs @@ -18,14 +18,16 @@ namespace Avalonia.Rendering _factory = factory; } + public int Count => _inner.Count; + public RenderLayer this[IVisual layerRoot] => _index[layerRoot]; + public RenderLayer Add(IVisual layerRoot, Size size) { RenderLayer result; if (!_index.TryGetValue(layerRoot, out result)) { - var bitmap = _factory.CreateLayer(layerRoot, size); - result = new RenderLayer(bitmap, size, layerRoot); + result = new RenderLayer(_factory, size, layerRoot); _inner.Add(result); _index.Add(layerRoot, result); } @@ -33,12 +35,7 @@ namespace Avalonia.Rendering return result; } - public RenderLayer Get(IVisual layerRoot) - { - RenderLayer result; - _index.TryGetValue(layerRoot, out result); - return result; - } + public bool Exists(IVisual layerRoot) => _index.ContainsKey(layerRoot); public void RemoveUnused(Scene scene) { @@ -55,6 +52,11 @@ namespace Avalonia.Rendering } } + public bool TryGetValue(IVisual layerRoot, out RenderLayer value) + { + return _index.TryGetValue(layerRoot, out value); + } + public IEnumerator GetEnumerator() => _inner.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 42836d56ea..b6cd56f964 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -21,6 +21,8 @@ namespace Avalonia.Rendering.SceneGraph { Contract.Requires(root != null); + var renderRoot = root.Visual as IRenderRoot; + _index = index; Root = root; Layers = layers; @@ -31,6 +33,7 @@ namespace Avalonia.Rendering.SceneGraph public int Id { get; } public SceneLayers Layers { get; } public IVisualNode Root { get; } + public Size Size { get; set; } public void Add(IVisualNode node) { @@ -42,8 +45,13 @@ namespace Avalonia.Rendering.SceneGraph public Scene Clone() { var index = new Dictionary(); - var root = (VisualNode)Clone((VisualNode)Root, null, index); - var result = new Scene(root, index, Layers.Clone(), Id + 1); + var root = Clone((VisualNode)Root, null, index); + + var result = new Scene(root, index, Layers.Clone(), Id + 1) + { + Size = Size, + }; + return result; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index ad92fc513a..8fe7b88ab6 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -16,6 +16,7 @@ namespace Avalonia.Rendering.SceneGraph Contract.Requires(scene != null); Dispatcher.UIThread.VerifyAccess(); + scene.Size = (scene.Root.Visual as IRenderRoot)?.ClientSize ?? scene.Root.Visual.Bounds.Size; scene.Layers.GetOrAdd(scene.Root.Visual); using (var impl = new DeferredDrawingContextImpl(scene.Layers)) @@ -33,6 +34,11 @@ namespace Avalonia.Rendering.SceneGraph var node = (VisualNode)scene.FindNode(visual); + if (visual == scene.Root.Visual) + { + UpdateSize(scene); + } + if (visual.VisualRoot != null) { if (visual.IsVisible) @@ -195,6 +201,37 @@ namespace Avalonia.Rendering.SceneGraph } } + private void UpdateSize(Scene scene) + { + var newSize = (scene.Root.Visual as IRenderRoot)?.ClientSize ?? scene.Root.Visual.Bounds.Size; + + if (scene.Size != newSize) + { + var oldSize = scene.Size; + + scene.Size = newSize; + + Rect horizontalDirtyRect = Rect.Empty; + Rect verticalDirtyRect = Rect.Empty; + + if (newSize.Width > oldSize.Width) + { + horizontalDirtyRect = new Rect(oldSize.Width, 0, newSize.Width - oldSize.Width, oldSize.Height); + } + + if (newSize.Height > oldSize.Height) + { + verticalDirtyRect = new Rect(0, oldSize.Height, newSize.Width, newSize.Height - oldSize.Height); + } + + foreach (var layer in scene.Layers) + { + layer.Dirty.Add(horizontalDirtyRect); + layer.Dirty.Add(verticalDirtyRect); + } + } + } + private static VisualNode CreateNode(Scene scene, IVisual visual, VisualNode parent) { var node = new VisualNode(visual, parent); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 588032ae57..c526f1ed04 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -82,7 +82,8 @@ namespace Avalonia.Direct2D1.Media /// public Bitmap WicImpl { - get; } + get; + } /// /// Gets a Direct2D bitmap to use on the specified render target. diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index 32dd4ac794..a81f67e2ce 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -8,6 +8,7 @@ using Avalonia.Platform; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.Direct2D1; +using SharpDX.Mathematics.Interop; using DrawingContext = Avalonia.Media.DrawingContext; using DwFactory = SharpDX.DirectWrite.Factory; diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 0e783e3f8d..aa46003456 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -163,6 +163,29 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Window_Resize_Notification_Should_Notify_Renderer_Dirty() + { + var renderer = new Mock(); + var services = TestServices.StyledWindow.With(renderer: (_, __) => renderer.Object); + + using (UnitTestApplication.Start(services)) + { + var impl = new Mock(); + impl.SetupAllProperties(); + impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); + impl.Setup(x => x.Scaling).Returns(1); + + var target = new TestTopLevel(impl.Object); + target.Measure(new Size(123, 456)); + Assert.True(target.IsMeasureValid); + + impl.Object.Resized(new Size(100, 200)); + + renderer.Verify(x => x.AddDirty(target)); + } + } + [Fact] public void Activate_Should_Call_Impl_Activate() { diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index d5f8022f81..0473ec11b3 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -42,7 +42,7 @@ namespace Avalonia.UnitTests public int NameScopeUnregisteredSubscribers { get; private set; } - public Size ClientSize => new Size(100, 100); + public Size ClientSize { get; set;} = new Size(100, 100); public Size MaxClientSize => Size.Infinity; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 26769b37a7..86ae7f016b 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -118,6 +118,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) .Callback(scene => { + scene.Size = root.ClientSize; scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); }); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index 1bc89b5112..2f0a636db5 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -510,6 +510,51 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph } } + [Fact] + public void Resizing_Scene_Should_Add_DirtyRects() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Child = decorator = new Decorator + { + Margin = new Thickness(0, 10, 0, 0), + Child = border = new Border + { + Opacity = 0.5, + Background = Brushes.Red, + Child = canvas = new Canvas(), + } + } + }; + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + Assert.Equal(new Size(100, 100), scene.Size); + + tree.ClientSize = new Size(110, 120); + scene = scene.Clone(); + sceneBuilder.Update(scene, tree); + + Assert.Equal(new Size(110, 120), scene.Size); + + var expected = new[] + { + new Rect(100, 0, 10, 100), + new Rect(0, 100, 110, 20), + }; + + Assert.Equal(expected, scene.Layers[tree].Dirty.ToArray()); + Assert.Equal(expected, scene.Layers[border].Dirty.ToArray()); + } + } + private IDisposable TestApplication() { return UnitTestApplication.Start(