From 1b9d61416f98f3237be6e007c21cc2520ff64216 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 1 Apr 2017 20:12:14 +0200 Subject: [PATCH 1/8] Ported ImmediateRenderer from scenegraph branch. --- samples/RenderTest/MainWindow.xaml.cs | 4 +- src/Avalonia.Controls/Control.cs | 36 +- src/Avalonia.Controls/TopLevel.cs | 15 +- .../Media/Imaging/RenderTargetBitmap.cs | 25 +- .../Platform/IPlatformRenderInterface.cs | 8 +- .../Platform/IRenderTarget.cs | 10 +- src/Avalonia.Visuals/Rendering/IRenderer.cs | 39 +- .../Rendering/IVisualBrushInitialize.cs | 20 + .../Rendering/IVisualBrushRenderer.cs | 31 ++ .../Rendering/ImmediateRenderer.cs | 278 ++++++++++ src/Avalonia.Visuals/Rendering/Renderer.cs | 67 --- .../Rendering/RendererBase.cs | 51 ++ .../Rendering/RendererMixin.cs | 205 ------- .../Utilities/TileBrushCalculator.cs | 193 +++++++ .../Rendering/ZIndexComparer.cs | 13 + src/Avalonia.Visuals/Visual.cs | 4 +- .../VisualTreeAttachmentEventArgs.cs | 11 +- src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj | 2 - src/Gtk/Avalonia.Cairo/CairoPlatform.cs | 2 +- .../Avalonia.Cairo/Media/DrawingContext.cs | 126 +++-- .../Avalonia.Cairo/Media/ImageBrushImpl.cs | 57 +- .../Media/Imaging/RenderTargetBitmapImpl.cs | 4 +- src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs | 55 -- .../Avalonia.Cairo/Media/VisualBrushImpl.cs | 15 - src/Gtk/Avalonia.Cairo/RenderTarget.cs | 9 +- src/Gtk/Avalonia.Gtk/GtkPlatform.cs | 2 +- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 7 +- .../RenderHelpers/RenderHelpers.projitems | 3 +- .../RenderHelpers/TileBrushImplHelper.cs | 209 ------- src/Skia/Avalonia.Skia/BitmapImpl.cs | 14 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 67 ++- .../Avalonia.Skia/FramebufferRenderTarget.cs | 12 +- .../Avalonia.Skia/PlatformRenderInterface.cs | 16 +- src/Skia/Avalonia.Skia/SkiaPlatform.cs | 3 +- .../Avalonia.Direct2D1.csproj | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 23 +- .../Media/DrawingContextImpl.cs | 105 +++- .../{TileBrushImpl.cs => ImageBrushImpl.cs} | 87 ++- .../Media/Imaging/RenderTargetBitmapImpl.cs | 20 +- .../Media/Imaging/WicBitmapImpl.cs | 11 +- .../Avalonia.Direct2D1/RenderTarget.cs | 11 +- .../SwapChainRenderTarget.cs | 18 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 8 +- .../InputElement_HitTesting.cs | 509 ------------------ .../Avalonia.RenderTests/Media/BitmapTests.cs | 6 +- .../Avalonia.UnitTests.csproj | 13 - .../MockPlatformRenderInterface.cs | 62 +++ .../MockStreamGeometryImpl.cs | 152 ++++++ tests/Avalonia.UnitTests/TestServices.cs | 8 +- .../Avalonia.UnitTests/UnitTestApplication.cs | 17 +- .../RenderTests_Culling.cs | 2 +- tests/Avalonia.Visuals.UnitTests/TestRoot.cs | 39 -- .../Avalonia.Visuals.UnitTests/VisualTests.cs | 105 +++- .../VisualTree/BoundsTrackerTests.cs | 2 +- .../VisualTree/MockRenderInterface.cs | 2 +- .../VisualExtensionsTests_GetVisualsAt.cs | 430 +++++++++------ 56 files changed, 1736 insertions(+), 1509 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/IVisualBrushInitialize.cs create mode 100644 src/Avalonia.Visuals/Rendering/IVisualBrushRenderer.cs create mode 100644 src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs delete mode 100644 src/Avalonia.Visuals/Rendering/Renderer.cs create mode 100644 src/Avalonia.Visuals/Rendering/RendererBase.cs delete mode 100644 src/Avalonia.Visuals/Rendering/RendererMixin.cs create mode 100644 src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs create mode 100644 src/Avalonia.Visuals/Rendering/ZIndexComparer.cs delete mode 100644 src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs delete mode 100644 src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs delete mode 100644 src/Shared/RenderHelpers/TileBrushImplHelper.cs rename src/Windows/Avalonia.Direct2D1/Media/{TileBrushImpl.cs => ImageBrushImpl.cs} (50%) delete mode 100644 tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs create mode 100644 tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs create mode 100644 tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs delete mode 100644 tests/Avalonia.Visuals.UnitTests/TestRoot.cs diff --git a/samples/RenderTest/MainWindow.xaml.cs b/samples/RenderTest/MainWindow.xaml.cs index c6c0eafd25..76a8e81aca 100644 --- a/samples/RenderTest/MainWindow.xaml.cs +++ b/samples/RenderTest/MainWindow.xaml.cs @@ -7,7 +7,6 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; using RenderTest.ViewModels; using ReactiveUI; -using Avalonia.Rendering; namespace RenderTest { @@ -19,7 +18,8 @@ namespace RenderTest this.AttachDevTools(); var vm = new MainWindowViewModel(); - vm.WhenAnyValue(x => x.DrawFps).Subscribe(x => RendererMixin.DrawFpsCounter = x); + vm.WhenAnyValue(x => x.DrawDirtyRects).Subscribe(x => Renderer.DrawDirtyRects = x); + vm.WhenAnyValue(x => x.DrawFps).Subscribe(x => Renderer.DrawFps = x); this.DataContext = vm; } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index ef253a28e2..516da813ef 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -17,7 +17,9 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Logging; using Avalonia.LogicalTree; +using Avalonia.Rendering; using Avalonia.Styling; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -33,7 +35,7 @@ namespace Avalonia.Controls /// - Implements to allow styling to work on the control. /// - Implements to form part of a logical tree. /// - public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize + public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize, IVisualBrushInitialize { /// /// Defines the property. @@ -460,6 +462,38 @@ namespace Avalonia.Controls InheritanceParent = parent; } + /// + void IVisualBrushInitialize.EnsureInitialized() + { + if (VisualRoot == null) + { + if (!IsInitialized) + { + foreach (var i in this.GetSelfAndVisualDescendents()) + { + var c = i as IControl; + + if (c?.IsInitialized == false) + { + var init = c as ISupportInitialize; + + if (init != null) + { + init.BeginInit(); + init.EndInit(); + } + } + } + } + + if (!IsArrangeValid) + { + Measure(Size.Infinity); + Arrange(new Rect(DesiredSize)); + } + } + } + /// /// Adds a pseudo-class to be set when a property is true. /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index eaa69b4d4b..8549a98371 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -96,10 +96,10 @@ namespace Avalonia.Controls PlatformImpl.Closed = HandleClosed; PlatformImpl.Input = HandleInput; - PlatformImpl.Paint = Renderer != null ? (Action)Renderer.Render : null; + PlatformImpl.Paint = HandlePaint; PlatformImpl.Resized = HandleResized; PlatformImpl.ScalingChanged = HandleScalingChanged; - + _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); @@ -208,6 +208,15 @@ namespace Avalonia.Controls return PlatformImpl.PointToScreen(p); } + /// + /// Handles a paint notification from . + /// + /// The dirty area. + protected virtual void HandlePaint(Rect rect) + { + Renderer?.Paint(rect); + } + /// /// Handles a closed notification from . /// @@ -229,7 +238,7 @@ namespace Avalonia.Controls Width = clientSize.Width; Height = clientSize.Height; LayoutManager.Instance.ExecuteLayoutPass(); - PlatformImpl.Invalidate(new Rect(clientSize)); + Renderer?.Resized(clientSize); } /// diff --git a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs index 4cc9c3fdf2..9d3f15f69a 100644 --- a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs @@ -16,10 +16,12 @@ namespace Avalonia.Media.Imaging /// /// Initializes a new instance of the class. /// - /// The width of the bitmap. - /// The height of the bitmap. - public RenderTargetBitmap(int width, int height) - : base(CreateImpl(width, height)) + /// The width of the bitmap in pixels. + /// The height of the bitmap in pixels. + /// The horizontal DPI of the bitmap. + /// The vertical DPI of the bitmap. + public RenderTargetBitmap(int pixelWidth, int pixelHeight, double dpiX = 96, double dpiY = 96) + : base(CreateImpl(pixelWidth, pixelHeight, dpiX, dpiY)) { } @@ -36,18 +38,27 @@ namespace Avalonia.Media.Imaging PlatformImpl.Dispose(); } + /// + /// Renders a visual to the . + /// + /// The visual to render. + public void Render(IVisual visual) => ImmediateRenderer.Render(visual, this); + /// /// Creates a platform-specific imlementation for a . /// /// The width of the bitmap. /// The height of the bitmap. + /// The horizontal DPI of the bitmap. + /// The vertical DPI of the bitmap. /// The platform-specific implementation. - private static IBitmapImpl CreateImpl(int width, int height) + private static IBitmapImpl CreateImpl(int width, int height, double dpiX, double dpiY) { IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - return factory.CreateRenderTargetBitmap(width, height); + return factory.CreateRenderTargetBitmap(width, height, dpiX, dpiY); } - public DrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext(); + /// + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer vbr) => PlatformImpl.CreateDrawingContext(vbr); } } diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index ba7b758484..aab8521f6d 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -51,8 +51,14 @@ namespace Avalonia.Platform /// /// The width of the bitmap. /// The height of the bitmap. + /// The horizontal DPI of the bitmap. + /// The vertical DPI of the bitmap. /// An . - IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height); + IRenderTargetBitmapImpl CreateRenderTargetBitmap( + int width, + int height, + double dpiX, + double dpiY); /// /// Creates a writable bitmap implementation. diff --git a/src/Avalonia.Visuals/Platform/IRenderTarget.cs b/src/Avalonia.Visuals/Platform/IRenderTarget.cs index 75ba9d5a56..522de64ec7 100644 --- a/src/Avalonia.Visuals/Platform/IRenderTarget.cs +++ b/src/Avalonia.Visuals/Platform/IRenderTarget.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Avalonia.Media; +using Avalonia.Rendering; namespace Avalonia.Platform { @@ -15,8 +15,12 @@ namespace Avalonia.Platform public interface IRenderTarget : IDisposable { /// - /// Creates an for a rendering session. + /// Creates an for a rendering session. /// - DrawingContext CreateDrawingContext(); + /// + /// A render to be used to render visual brushes. May be null if no visual brushes are + /// to be drawn. + /// + IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer); } } diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs index c643662179..8d2fc3a0c1 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs @@ -3,13 +3,50 @@ using System; using Avalonia.VisualTree; +using System.Collections.Generic; namespace Avalonia.Rendering { + /// + /// Defines the interface for a renderer. + /// public interface IRenderer : IDisposable { + /// + /// Gets or sets a value indicating whether the renderer should draw an FPS counter. + /// + bool DrawFps { get; set; } + + /// + /// Gets or sets a value indicating whether the renderer should a visual representation + /// of its dirty rectangles. + /// + bool DrawDirtyRects { get; set; } + + /// + /// Mark a visual as dirty and needing re-rendering. + /// + /// The visual. void AddDirty(IVisual visual); - void Render(Rect rect); + /// + /// Hit tests a location to find the visuals at the specified point. + /// + /// The point, in client coordinates. + /// An optional filter. + /// The visuals at the specified point, topmost first. + IEnumerable HitTest(Point p, Func filter); + + /// + /// Called when a resize notification is received by the control being rendered. + /// + /// The new size of the window. + void Resized(Size size); + + /// + /// Called when a paint notification is received by the control being rendered. + /// + /// The dirty rectangle. + void Paint(Rect rect); } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/Rendering/IVisualBrushInitialize.cs b/src/Avalonia.Visuals/Rendering/IVisualBrushInitialize.cs new file mode 100644 index 0000000000..b3209e2462 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/IVisualBrushInitialize.cs @@ -0,0 +1,20 @@ +// 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.Media; + +namespace Avalonia.Rendering +{ + /// + /// Internal interface for initializing controls that are to be used as the viusal in a + /// . + /// + public interface IVisualBrushInitialize + { + /// + /// Ensures that the control is ready to use as the visual in a visual brush. + /// + void EnsureInitialized(); + } +} diff --git a/src/Avalonia.Visuals/Rendering/IVisualBrushRenderer.cs b/src/Avalonia.Visuals/Rendering/IVisualBrushRenderer.cs new file mode 100644 index 0000000000..3d9b20713c --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/IVisualBrushRenderer.cs @@ -0,0 +1,31 @@ +// 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.Media; +using Avalonia.Platform; + +namespace Avalonia.Rendering +{ + /// + /// Defines a renderer used to render a visual brush to a bitmap. + /// + public interface IVisualBrushRenderer + { + /// + /// Gets the size of the intermediate render target to which the visual brush should be + /// drawn. + /// + /// The visual brush. + /// The size of the intermediate render target to create. + Size GetRenderTargetSize(IVisualBrush brush); + + /// + /// Renders a visual brush to a bitmap. + /// + /// The drawing context to render to. + /// The visual brush. + /// A bitmap containing the rendered brush. + void RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush); + } +} diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs new file mode 100644 index 0000000000..af51eacaed --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -0,0 +1,278 @@ +// 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; +using System.Collections.Generic; +using Avalonia.Media; +using System.Linq; + +namespace Avalonia.Rendering +{ + /// + /// A renderer which renders the state of the visual tree without an intermediate scene graph + /// representation. + /// + /// + /// The immediate renderer supports only clip-bound-based hit testing; a control's geometry is + /// not taken into account. + /// + public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer + { + private readonly IVisual _root; + private readonly IRenderRoot _renderRoot; + private IRenderTarget _renderTarget; + + /// + /// Initializes a new instance of the class. + /// + /// The control to render. + public ImmediateRenderer(IVisual root) + { + Contract.Requires(root != null); + + _root = root; + _renderRoot = root as IRenderRoot; + } + + /// + public bool DrawFps { get; set; } + + /// + public bool DrawDirtyRects { get; set; } + + /// + public void Paint(Rect rect) + { + if (_renderTarget == null) + { + _renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + } + + try + { + using (var context = new DrawingContext(_renderTarget.CreateDrawingContext(this))) + { + using (context.PushTransformContainer()) + { + Render(context, _root, _root.Bounds); + } + + if (DrawDirtyRects) + { + var color = (uint)new Random().Next(0xffffff) | 0x44000000; + context.FillRectangle( + new SolidColorBrush(color), + rect); + } + + if (DrawFps) + { + RenderFps(context.PlatformImpl, _root.Bounds, true); + } + } + } + catch (RenderTargetCorruptedException ex) + { + Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); + _renderTarget.Dispose(); + _renderTarget = null; + } + } + + /// + public void Resized(Size size) + { + } + + /// + /// Renders a visual to a render target. + /// + /// The visual. + /// The render target. + public static void Render(IVisual visual, IRenderTarget target) + { + using (var renderer = new ImmediateRenderer(visual)) + using (var context = new DrawingContext(target.CreateDrawingContext(renderer))) + { + renderer.Render(context, visual, visual.Bounds); + } + } + + /// + /// Renders a visual to a drawing context. + /// + /// The visual. + /// The drawing context. + public static void Render(IVisual visual, DrawingContext context) + { + using (var renderer = new ImmediateRenderer(visual)) + { + renderer.Render(context, visual, visual.Bounds); + } + } + + /// + public void AddDirty(IVisual visual) + { + if (visual.Bounds != Rect.Empty) + { + var m = visual.TransformToVisual(_root); + + if (m.HasValue) + { + var bounds = new Rect(visual.Bounds.Size).TransformToAABB(m.Value); + _renderRoot?.Invalidate(bounds); + } + } + } + + /// + /// Ends the operation of the renderer. + /// + public void Dispose() + { + } + + /// + public IEnumerable HitTest(Point p, Func filter) + { + return HitTest(_root, p, filter); + } + + /// + Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) + { + (brush.Visual as IVisualBrushInitialize)?.EnsureInitialized(); + return brush.Visual?.Bounds.Size ?? Size.Empty; + } + + /// + void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) + { + var visual = brush.Visual; + Render(new DrawingContext(context), visual, visual.Bounds); + } + + 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, + Func filter) + { + Contract.Requires(visual != null); + + if (filter?.Invoke(visual) != false) + { + bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual)?.Contains(p) == true; + + if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0) + { + foreach (var child in visual.VisualChildren.SortByZIndex()) + { + foreach (var result in HitTest(child, p, filter)) + { + yield return result; + } + } + } + + if (containsPoint) + { + yield return visual; + } + } + } + + 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); + } + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/Renderer.cs b/src/Avalonia.Visuals/Rendering/Renderer.cs deleted file mode 100644 index ed58d129af..0000000000 --- a/src/Avalonia.Visuals/Rendering/Renderer.cs +++ /dev/null @@ -1,67 +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 Avalonia.Platform; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - public class Renderer : IDisposable, IRenderer - { - private readonly IRenderLoop _renderLoop; - private readonly IRenderRoot _root; - private IRenderTarget _renderTarget; - private bool _dirty; - - public Renderer(IRenderRoot root, IRenderLoop renderLoop) - { - Contract.Requires(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; - } - finally - { - _dirty = false; - } - } - - private void OnRenderLoopTick(object sender, EventArgs e) - { - if (_dirty) - { - _root.Invalidate(new Rect(_root.ClientSize)); - } - } - } -} diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs new file mode 100644 index 0000000000..707b31998a --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs @@ -0,0 +1,51 @@ +using System; +using System.Diagnostics; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.Rendering +{ + public class RendererBase + { + private static readonly Typeface s_fpsTypeface = new Typeface("Arial", 18); + private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + private int _framesThisSecond; + private int _fps; + private FormattedText _fpsText; + private TimeSpan _lastFpsUpdate; + + public RendererBase() + { + _fpsText = new FormattedText + { + Typeface = new Typeface(null, 18), + }; + } + + protected void RenderFps(IDrawingContextImpl context, Rect clientRect, bool incrementFrameCount) + { + var now = _stopwatch.Elapsed; + var elapsed = now - _lastFpsUpdate; + + if (incrementFrameCount) + { + ++_framesThisSecond; + } + + if (elapsed.TotalSeconds > 1) + { + _fps = (int)(_framesThisSecond / elapsed.TotalSeconds); + _framesThisSecond = 0; + _lastFpsUpdate = now; + } + + _fpsText.Text = string.Format("FPS: {0:000}", _fps); + var size = _fpsText.Measure(); + var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height); + + context.Transform = Matrix.Identity; + context.FillRectangle(Brushes.Black, rect); + context.DrawText(Brushes.White, rect.TopLeft, _fpsText.PlatformImpl); + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/RendererMixin.cs b/src/Avalonia.Visuals/Rendering/RendererMixin.cs deleted file mode 100644 index a80f89930b..0000000000 --- a/src/Avalonia.Visuals/Rendering/RendererMixin.cs +++ /dev/null @@ -1,205 +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 = 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 - { - Text = "Frame #" + s_frameNum + " FPS: " + s_fps, - Typeface = new Typeface("Arial", 18) - }; - 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/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs b/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs new file mode 100644 index 0000000000..52a8fb5ab7 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs @@ -0,0 +1,193 @@ +// 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 Avalonia.Media; + +namespace Avalonia.Rendering.Utilities +{ + public class TileBrushCalculator + { + private readonly Size _imageSize; + private readonly Rect _drawRect; + + public bool IsValid { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The brush to be rendered. + /// The size of the content of the tile brush. + /// The size of the control to which the brush is being rendered. + public TileBrushCalculator(ITileBrush brush, Size contentSize, Size targetSize) + : this( + brush.TileMode, + brush.Stretch, + brush.AlignmentX, + brush.AlignmentY, + brush.SourceRect, + brush.DestinationRect, + contentSize, + targetSize) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush's tile mode. + /// The brush's stretch. + /// The brush's horizontal alignment. + /// The brush's vertical alignment. + /// The brush's source rect + /// The brush's destination rect. + /// The size of the content of the tile brush. + /// The size of the control to which the brush is being rendered. + public TileBrushCalculator( + TileMode tileMode, + Stretch stretch, + AlignmentX alignmentX, + AlignmentY alignmentY, + RelativeRect sourceRect, + RelativeRect destinationRect, + Size contentSize, + Size targetSize) + { + _imageSize = contentSize; + + SourceRect = sourceRect.ToPixels(_imageSize); + DestinationRect = destinationRect.ToPixels(targetSize); + + var scale = stretch.CalculateScaling(DestinationRect.Size, SourceRect.Size); + var translate = CalculateTranslate(alignmentX, alignmentY, SourceRect, DestinationRect, scale); + + IntermediateSize = tileMode == TileMode.None ? targetSize : DestinationRect.Size; + IntermediateTransform = CalculateIntermediateTransform( + tileMode, + SourceRect, + DestinationRect, + scale, + translate, + out _drawRect); + } + + /// + /// Gets the rectangle on the destination control to which content should be rendered. + /// + /// + /// If of the brush is repeating then this is describes rectangle + /// of a single repeat of the tiled content. + /// + public Rect DestinationRect { get; } + + /// + /// Gets the clip rectangle on the intermediate image with which the brush content should be + /// drawn when is true. + /// + public Rect IntermediateClip => _drawRect; + + /// + /// Gets the size of the intermediate image that should be created when + /// is true. + /// + public Size IntermediateSize { get; } + + /// + /// Gets the transform to be used when rendering to the intermediate image when + /// is true. + /// + public Matrix IntermediateTransform { get; } + + /// + /// Gets a value indicating whether an intermediate image should be created in order to + /// render the tile brush. + /// + /// + /// Intermediate images are required when a brush's is not repeating + /// but the source and destination aspect ratios are unequal, as all of the currently + /// supported rendering backends do not support non-tiled image brushes. + /// + public bool NeedsIntermediate + { + get + { + if (IntermediateTransform != Matrix.Identity) + return true; + if (SourceRect.Position != default(Point)) + return true; + if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio) + return false; + if ((int)SourceRect.Width != _imageSize.Width || + (int)SourceRect.Height != _imageSize.Height) + return true; + return false; + } + } + + /// + /// Gets the area of the source content to be rendered. + /// + public Rect SourceRect { get; } + + public static Vector CalculateTranslate( + AlignmentX alignmentX, + AlignmentY alignmentY, + Rect sourceRect, + Rect destinationRect, + Vector scale) + { + var x = 0.0; + var y = 0.0; + var size = sourceRect.Size * scale; + + switch (alignmentX) + { + case AlignmentX.Center: + x += (destinationRect.Width - size.Width) / 2; + break; + case AlignmentX.Right: + x += destinationRect.Width - size.Width; + break; + } + + switch (alignmentY) + { + case AlignmentY.Center: + y += (destinationRect.Height - size.Height) / 2; + break; + case AlignmentY.Bottom: + y += destinationRect.Height - size.Height; + break; + } + + return new Vector(x, y); + } + + public static Matrix CalculateIntermediateTransform( + TileMode tileMode, + Rect sourceRect, + Rect destinationRect, + Vector scale, + Vector translate, + out Rect drawRect) + { + var transform = Matrix.CreateTranslation(-sourceRect.Position) * + Matrix.CreateScale(scale) * + Matrix.CreateTranslation(translate); + Rect dr; + + if (tileMode == TileMode.None) + { + dr = destinationRect; + transform *= Matrix.CreateTranslation(destinationRect.Position); + } + else + { + dr = new Rect(destinationRect.Size); + } + + drawRect = dr; + + return transform; + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs b/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs new file mode 100644 index 0000000000..b9c43bcbc3 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using Avalonia.VisualTree; + +namespace Avalonia.Rendering +{ + public class ZIndexComparer : IComparer + { + public static readonly ZIndexComparer Instance = new ZIndexComparer(); + + public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex); + } +} diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index dbc678eee5..ae5e385169 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -498,14 +498,14 @@ namespace Avalonia if (VisualRoot != null) { - var e = new VisualTreeAttachmentEventArgs(VisualRoot); + var e = new VisualTreeAttachmentEventArgs(old, VisualRoot); OnDetachedFromVisualTreeCore(e); } if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) { var root = this.GetVisualAncestors().OfType().FirstOrDefault(); - var e = new VisualTreeAttachmentEventArgs(root); + var e = new VisualTreeAttachmentEventArgs(_visualParent, root); OnAttachedToVisualTreeCore(e); } diff --git a/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs b/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs index 7a3b97f55b..8c6e660d10 100644 --- a/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs +++ b/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Rendering; +using Avalonia.VisualTree; namespace Avalonia { @@ -15,14 +16,22 @@ namespace Avalonia /// /// Initializes a new instance of the class. /// + /// The parent that the visual is being attached to or detached from. /// The root visual. - public VisualTreeAttachmentEventArgs(IRenderRoot root) + public VisualTreeAttachmentEventArgs(IVisual parent, IRenderRoot root) { + Contract.Requires(parent != null); Contract.Requires(root != null); + Parent = parent; Root = root; } + /// + /// Gets the parent that the visual is being attached to or detached from. + /// + public IVisual Parent { get; } + /// /// Gets the root of the visual tree that the visual is being attached to or detached from. /// diff --git a/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj b/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj index 45b4f06c8c..cc6684f622 100644 --- a/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj +++ b/src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj @@ -53,7 +53,6 @@ - @@ -63,7 +62,6 @@ - diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index dde92d5870..8a68d58e20 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -68,7 +68,7 @@ namespace Avalonia.Cairo "Don't know how to create a Cairo renderer from any of the provided surfaces.")); } - public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height, double dpiX, double dpiY) { return new RenderTargetBitmapImpl(new ImageSurface(Format.Argb32, width, height)); } diff --git a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs index 56fb45dbef..7d1776db0b 100644 --- a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs @@ -8,6 +8,7 @@ using System.Reactive.Disposables; using Avalonia.Cairo.Media.Imaging; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Rendering; namespace Avalonia.Cairo.Media { @@ -18,32 +19,30 @@ namespace Avalonia.Cairo.Media /// public class DrawingContext : IDrawingContextImpl, IDisposable { - /// - /// The cairo context. - /// private readonly Cairo.Context _context; - + private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly Stack _maskStack = new Stack(); /// /// Initializes a new instance of the class. /// /// The target surface. - public DrawingContext(Cairo.Surface surface) + public DrawingContext(Cairo.Surface surface, IVisualBrushRenderer visualBrushRenderer) { _context = new Cairo.Context(surface); + _visualBrushRenderer = visualBrushRenderer; } /// /// Initializes a new instance of the class. /// /// The GDK drawable. - public DrawingContext(Gdk.Drawable drawable) + public DrawingContext(Gdk.Drawable drawable, IVisualBrushRenderer visualBrushRenderer) { _context = Gdk.CairoHelper.Create(drawable); + _visualBrushRenderer = visualBrushRenderer; } - private Matrix _transform = Matrix.Identity; /// /// Gets the current transform of the drawing context. @@ -55,7 +54,7 @@ namespace Avalonia.Cairo.Media { _transform = value; _context.Matrix = value.ToCairo(); - + } } @@ -82,42 +81,47 @@ namespace Avalonia.Cairo.Media /// The rect in the output to draw to. public void DrawImage(IBitmapImpl bitmap, double opacity, Rect sourceRect, Rect destRect) { - var impl = bitmap as BitmapImpl; - var size = new Size(impl.PixelWidth, impl.PixelHeight); + var pixbuf = bitmap as Gdk.Pixbuf; + var rtb = bitmap as RenderTargetBitmapImpl; + var size = new Size(pixbuf?.Width ?? rtb.PixelWidth, pixbuf?.Height ?? rtb.PixelHeight); var scale = new Vector(destRect.Width / sourceRect.Width, destRect.Height / sourceRect.Height); _context.Save(); _context.Scale(scale.X, scale.Y); destRect /= scale; - if (opacityOverride < 1.0f) { - _context.PushGroup (); - Gdk.CairoHelper.SetSourcePixbuf ( - _context, - impl, - -sourceRect.X + destRect.X, - -sourceRect.Y + destRect.Y); - - _context.Rectangle (destRect.ToCairo ()); - _context.Fill (); - _context.PopGroupToSource (); - _context.PaintWithAlpha (opacityOverride); - } else { - _context.PushGroup (); - Gdk.CairoHelper.SetSourcePixbuf ( - _context, - impl, - -sourceRect.X + destRect.X, - -sourceRect.Y + destRect.Y); - - _context.Rectangle (destRect.ToCairo ()); - _context.Fill (); - _context.PopGroupToSource (); - _context.PaintWithAlpha (opacityOverride); + _context.PushGroup(); + + if (pixbuf != null) + { + Gdk.CairoHelper.SetSourcePixbuf( + _context, + pixbuf, + -sourceRect.X + destRect.X, + -sourceRect.Y + destRect.Y); + } + else + { + _context.SetSourceSurface( + rtb.Surface, + (int)(-sourceRect.X + destRect.X), + (int)(-sourceRect.Y + destRect.Y)); } + + _context.Rectangle(destRect.ToCairo()); + _context.Fill(); + _context.PopGroupToSource(); + _context.PaintWithAlpha(opacityOverride); _context.Restore(); } + public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) + { + PushOpacityMask(opacityMask, opacityMaskRect); + DrawImage(source, 1, new Rect(0, 0, source.PixelWidth, source.PixelHeight), destRect); + PopOpacityMask(); + } + /// /// Draws a line. /// @@ -127,8 +131,8 @@ namespace Avalonia.Cairo.Media public void DrawLine(Pen pen, Point p1, Point p2) { var size = new Rect(p1, p2).Size; - - using (var p = SetPen(pen, size)) + + using (var p = SetPen(pen, size)) { _context.MoveTo(p1.ToCairo()); _context.LineTo(p2.ToCairo()); @@ -149,7 +153,7 @@ namespace Avalonia.Cairo.Media var oldMatrix = Transform; Transform = impl.Transform * Transform; - + if (brush != null) { _context.AppendPath(impl.Path); @@ -184,9 +188,9 @@ namespace Avalonia.Cairo.Media /// The rectangle bounds. public void DrawRectangle(Pen pen, Rect rect, float cornerRadius) { - using (var p = SetPen(pen, rect.Size)) + using (var p = SetPen(pen, rect.Size)) { - _context.Rectangle(rect.ToCairo ()); + _context.Rectangle(rect.ToCairo()); _context.Stroke(); } } @@ -202,7 +206,7 @@ namespace Avalonia.Cairo.Media var layout = ((FormattedTextImpl)text).Layout; _context.MoveTo(origin.X, origin.Y); - using (var b = SetBrush(foreground, new Size(0, 0))) + using (var b = SetBrush(foreground, new Size(0, 0))) { Pango.CairoHelper.ShowLayout(_context, layout); } @@ -215,9 +219,9 @@ namespace Avalonia.Cairo.Media /// The rectangle bounds. public void FillRectangle(IBrush brush, Rect rect, float cornerRadius) { - using (var b = SetBrush(brush, rect.Size)) + using (var b = SetBrush(brush, rect.Size)) { - _context.Rectangle(rect.ToCairo ()); + _context.Rectangle(rect.ToCairo()); _context.Fill(); } } @@ -272,10 +276,10 @@ namespace Avalonia.Cairo.Media return Disposable.Create(() => { - _context.Restore(); + _context.Restore(); }); } - + private double opacityOverride = 1.0f; private IDisposable SetBrush(IBrush brush, Size destinationSize) @@ -315,11 +319,35 @@ namespace Avalonia.Cairo.Media } else if (imageBrush != null) { - impl = new ImageBrushImpl(imageBrush, destinationSize); + impl = new ImageBrushImpl(imageBrush, (BitmapImpl)imageBrush.Source.PlatformImpl, destinationSize); } else if (visualBrush != null) { - impl = new VisualBrushImpl(visualBrush, destinationSize); + if (_visualBrushRenderer != null) + { + var intermediateSize = _visualBrushRenderer.GetRenderTargetSize(visualBrush); + + if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1) + { + using (var intermediate = new Cairo.ImageSurface(Cairo.Format.ARGB32, (int)intermediateSize.Width, (int)intermediateSize.Height)) + { + using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer)) + { + ctx.Clear(Colors.Transparent); + _visualBrushRenderer.RenderVisualBrush(ctx, visualBrush); + } + + return new ImageBrushImpl( + visualBrush, + new RenderTargetBitmapImpl(intermediate), + destinationSize); + } + } + } + else + { + throw new NotSupportedException("No IVisualBrushRenderer was supplied to DrawingContextImpl."); + } } else { @@ -351,7 +379,7 @@ namespace Avalonia.Cairo.Media if (pen.Brush == null) return Disposable.Empty; - + return SetBrush(pen.Brush, destinationSize); } @@ -377,10 +405,10 @@ namespace Avalonia.Cairo.Media public void PopOpacityMask() { _context.PopGroupToSource(); - var brushImpl = _maskStack.Pop (); + var brushImpl = _maskStack.Pop(); _context.Mask(brushImpl.PlatformBrush); - brushImpl.Dispose (); + brushImpl.Dispose(); } } } diff --git a/src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs b/src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs index 0ee7c630aa..14c9dee9e2 100644 --- a/src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs +++ b/src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs @@ -1,15 +1,56 @@ -using System; using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering.Utilities; using global::Cairo; namespace Avalonia.Cairo.Media { - public class ImageBrushImpl : BrushImpl - { - public ImageBrushImpl(IImageBrush brush, Size destinationSize) - { - this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize); - } - } + public class ImageBrushImpl : BrushImpl + { + public ImageBrushImpl( + ITileBrush brush, + IBitmapImpl bitmap, + Size targetSize) + { + var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize); + + using (var intermediate = new ImageSurface(Format.ARGB32, (int)calc.IntermediateSize.Width, (int)calc.IntermediateSize.Height)) + { + using (var context = new RenderTarget(intermediate).CreateDrawingContext(null)) + { + var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); + + context.Clear(Colors.Transparent); + context.PushClip(calc.IntermediateClip); + context.Transform = calc.IntermediateTransform; + context.DrawImage(bitmap, 1, rect, rect); + context.PopClip(); + } + + var result = new SurfacePattern(intermediate); + + if ((brush.TileMode & TileMode.FlipXY) != 0) + { + // TODO: Currently always FlipXY as that's all cairo supports natively. + // Support separate FlipX and FlipY by drawing flipped images to intermediate + // surface. + result.Extend = Extend.Reflect; + } + else + { + result.Extend = Extend.Repeat; + } + + if (brush.TileMode != TileMode.None) + { + var matrix = result.Matrix; + matrix.InitTranslate(-calc.DestinationRect.X, -calc.DestinationRect.Y); + result.Matrix = matrix; + } + + PlatformBrush = result; + } + } + } } diff --git a/src/Gtk/Avalonia.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Gtk/Avalonia.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs index db0aa082cc..fa8a3132d5 100644 --- a/src/Gtk/Avalonia.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Gtk/Avalonia.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs @@ -40,9 +40,9 @@ namespace Avalonia.Cairo.Media.Imaging Surface.WriteToPng(fileName); } - public Avalonia.Media.DrawingContext CreateDrawingContext() + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return _renderTarget.CreateDrawingContext(); + return _renderTarget.CreateDrawingContext(visualBrushRenderer); } public void Save(Stream stream) diff --git a/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs b/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs deleted file mode 100644 index eb78c9d2f3..0000000000 --- a/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs +++ /dev/null @@ -1,55 +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 Cairo; -using Avalonia.Cairo.Media.Imaging; -using Avalonia.Layout; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.RenderHelpers; - -namespace Avalonia.Cairo.Media -{ - internal static class TileBrushes - { - public static SurfacePattern CreateTileBrush(ITileBrush brush, Size targetSize) - { - var helper = new TileBrushImplHelper(brush, targetSize); - if (!helper.IsValid) - return null; - - using (var intermediate = new ImageSurface(Format.ARGB32, (int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height)) - using (var ctx = new RenderTarget(intermediate).CreateDrawingContext()) - { - helper.DrawIntermediate(ctx); - - var result = new SurfacePattern(intermediate); - - if ((brush.TileMode & TileMode.FlipXY) != 0) - { - // TODO: Currently always FlipXY as that's all cairo supports natively. - // Support separate FlipX and FlipY by drawing flipped images to intermediate - // surface. - result.Extend = Extend.Reflect; - } - else - { - result.Extend = Extend.Repeat; - } - - if (brush.TileMode != TileMode.None) - { - var matrix = result.Matrix; - matrix.InitTranslate(-helper.DestinationRect.X, -helper.DestinationRect.Y); - result.Matrix = matrix; - } - - return result; - } - } - - - } -} diff --git a/src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs b/src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs deleted file mode 100644 index e820c50420..0000000000 --- a/src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Avalonia.Media; -using global::Cairo; - -namespace Avalonia.Cairo.Media -{ - public class VisualBrushImpl : BrushImpl - { - public VisualBrushImpl(IVisualBrush brush, Size destinationSize) - { - this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize); - } - } -} - diff --git a/src/Gtk/Avalonia.Cairo/RenderTarget.cs b/src/Gtk/Avalonia.Cairo/RenderTarget.cs index 49f5e18dec..b18c07377b 100644 --- a/src/Gtk/Avalonia.Cairo/RenderTarget.cs +++ b/src/Gtk/Avalonia.Cairo/RenderTarget.cs @@ -42,15 +42,14 @@ namespace Avalonia.Cairo /// /// Creates a cairo surface that targets a platform-specific resource. /// + /// The visual brush renderer to use. /// A surface wrapped in an . - public DrawingContext CreateDrawingContext() => new DrawingContext(CreateMediaDrawingContext()); - - public IDrawingContextImpl CreateMediaDrawingContext() + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { if (_drawableAccessor != null) - return new Media.DrawingContext(_drawableAccessor()); + return new Media.DrawingContext(_drawableAccessor(), visualBrushRenderer); if (_surface != null) - return new Media.DrawingContext(_surface); + return new Media.DrawingContext(_surface, visualBrushRenderer); throw new InvalidOperationException("Unspecified render target"); } diff --git a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs index 38bada2f25..d387ed0320 100644 --- a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs +++ b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs @@ -115,7 +115,7 @@ namespace Avalonia.Gtk public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) { - return new Renderer(root, renderLoop); + return new ImmediateRenderer(root); } public IWindowIconImpl LoadIcon(string fileName) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 11209825dc..0f54bb740f 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -15,7 +15,7 @@ using Avalonia.Gtk3; namespace Avalonia.Gtk3 { - public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface + public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface, IRendererFactory { internal static readonly Gtk3Platform Instance = new Gtk3Platform(); internal static readonly MouseDevice Mouse = new MouseDevice(); @@ -52,7 +52,10 @@ namespace Avalonia.Gtk3 public IPopupImpl CreatePopup() => new PopupImpl(); - + public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) + { + return new ImmediateRenderer(root); + } public Size DoubleClickSize => new Size(4, 4); diff --git a/src/Shared/RenderHelpers/RenderHelpers.projitems b/src/Shared/RenderHelpers/RenderHelpers.projitems index 8be6300957..c088097a9f 100644 --- a/src/Shared/RenderHelpers/RenderHelpers.projitems +++ b/src/Shared/RenderHelpers/RenderHelpers.projitems @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -11,6 +11,5 @@ - \ No newline at end of file diff --git a/src/Shared/RenderHelpers/TileBrushImplHelper.cs b/src/Shared/RenderHelpers/TileBrushImplHelper.cs deleted file mode 100644 index ad1dc44c61..0000000000 --- a/src/Shared/RenderHelpers/TileBrushImplHelper.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 Avalonia.Controls; -using Avalonia.Media; -using Avalonia.Rendering; -using Avalonia.VisualTree; - -namespace Avalonia.RenderHelpers -{ - internal class TileBrushImplHelper - { - public Size IntermediateSize { get; } - public Rect DestinationRect { get; } - private readonly TileMode _tileMode; - private readonly Rect _sourceRect; - private readonly Vector _scale; - private readonly Vector _translate; - private readonly Size _imageSize; - private readonly IVisualBrush _visualBrush; - private readonly IImageBrush _imageBrush; - private readonly Matrix _transform; - private readonly Rect _drawRect; - - public bool IsValid { get; } - - public TileBrushImplHelper(ITileBrush brush, Size targetSize) - { - _imageBrush = brush as IImageBrush; - _visualBrush = brush as IVisualBrush; - if (_imageBrush != null) - { - if (_imageBrush.Source == null) - return; - _imageSize = new Size(_imageBrush.Source.PixelWidth, _imageBrush.Source.PixelHeight); - IsValid = true; - } - else if (_visualBrush != null) - { - var control = _visualBrush.Visual as IControl; - - if (control != null) - { - EnsureInitialized(control); - - if (control.IsArrangeValid == false) - { - control.Measure(Size.Infinity); - control.Arrange(new Rect(control.DesiredSize)); - } - - _imageSize = control.Bounds.Size; - IsValid = true; - } - } - else - return; - - _tileMode = brush.TileMode; - _sourceRect = brush.SourceRect.ToPixels(_imageSize); - DestinationRect = brush.DestinationRect.ToPixels(targetSize); - _scale = brush.Stretch.CalculateScaling(DestinationRect.Size, _sourceRect.Size); - _translate = CalculateTranslate(brush, _sourceRect, DestinationRect, _scale); - IntermediateSize = CalculateIntermediateSize(_tileMode, targetSize, DestinationRect.Size); - _transform = CalculateIntermediateTransform( - _tileMode, - _sourceRect, - DestinationRect, - _scale, - _translate, - out _drawRect); - } - - public bool NeedsIntermediateSurface - { - get - { - if (_imageBrush == null) - return true; - if (_transform != Matrix.Identity) - return true; - if (_sourceRect.Position != default(Point)) - return true; - if ((int) _sourceRect.Width != _imageBrush.Source.PixelWidth || - (int) _sourceRect.Height != _imageBrush.Source.PixelHeight) - return true; - return false; - } - } - - public T GetDirect() => (T) _imageBrush?.Source.PlatformImpl; - - public void DrawIntermediate(DrawingContext ctx) - { - using (ctx.PushClip(_drawRect)) - using (ctx.PushPostTransform(_transform)) - { - if (_imageBrush != null) - { - var bmpRc = new Rect(0, 0, _imageBrush.Source.PixelWidth, _imageBrush.Source.PixelHeight); - ctx.DrawImage(_imageBrush.Source, 1, bmpRc, bmpRc); - } - else if (_visualBrush != null) - { - using (ctx.PushPostTransform(Matrix.CreateTranslation(-_visualBrush.Visual.Bounds.Position))) - { - ctx.Render(_visualBrush.Visual); - } - } - } - } - - - /// - /// Calculates a translate based on an , a source and destination - /// rectangle and a scale. - /// - /// The brush. - /// The source rectangle. - /// The destination rectangle. - /// The _scale factor. - /// A vector with the X and Y _translate. - - public static Vector CalculateTranslate( - ITileBrush brush, - Rect sourceRect, - Rect destinationRect, - Vector scale) - { - var x = 0.0; - var y = 0.0; - var size = sourceRect.Size*scale; - - switch (brush.AlignmentX) - { - case AlignmentX.Center: - x += (destinationRect.Width - size.Width)/2; - break; - case AlignmentX.Right: - x += destinationRect.Width - size.Width; - break; - } - - switch (brush.AlignmentY) - { - case AlignmentY.Center: - y += (destinationRect.Height - size.Height)/2; - break; - case AlignmentY.Bottom: - y += destinationRect.Height - size.Height; - break; - } - - return new Vector(x, y); - } - - public static Matrix CalculateIntermediateTransform( - TileMode tileMode, - Rect sourceRect, - Rect destinationRect, - Vector scale, - Vector translate, - out Rect drawRect) - { - var transform = Matrix.CreateTranslation(-sourceRect.Position)* - Matrix.CreateScale(scale)* - Matrix.CreateTranslation(translate); - Rect dr; - - if (tileMode == TileMode.None) - { - dr = destinationRect; - transform *= Matrix.CreateTranslation(destinationRect.Position); - } - else - { - dr = new Rect(destinationRect.Size); - } - - drawRect = dr; - - return transform; - } - - private static Size CalculateIntermediateSize( - TileMode tileMode, - Size targetSize, - Size destinationSize) => tileMode == TileMode.None ? targetSize : destinationSize; - - private static void EnsureInitialized(IControl control) - { - foreach (var i in control.GetSelfAndVisualDescendents()) - { - var c = i as IControl; - - if (c?.IsInitialized == false) - { - var init = c as ISupportInitialize; - - if (init != null) - { - init.BeginInit(); - init.EndInit(); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index b564734a47..9d46855a58 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using System.Text; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Rendering; using SkiaSharp; namespace Avalonia.Skia @@ -66,9 +67,10 @@ namespace Avalonia.Skia { private readonly SKSurface _surface; - public BitmapDrawingContext(SKBitmap bitmap) : this(CreateSurface(bitmap)) + public BitmapDrawingContext(SKBitmap bitmap, IVisualBrushRenderer visualBrushRenderer) + : this(CreateSurface(bitmap), visualBrushRenderer) { - + } private static SKSurface CreateSurface(SKBitmap bitmap) @@ -80,7 +82,8 @@ namespace Avalonia.Skia return rv; } - public BitmapDrawingContext(SKSurface surface) : base(surface.Canvas) + public BitmapDrawingContext(SKSurface surface, IVisualBrushRenderer visualBrushRenderer) + : base(surface.Canvas, visualBrushRenderer) { _surface = surface; } @@ -92,10 +95,9 @@ namespace Avalonia.Skia } } - public DrawingContext CreateDrawingContext() + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - - return new DrawingContext(new BitmapDrawingContext(Bitmap)); + return new BitmapDrawingContext(Bitmap, visualBrushRenderer); } public void Save(Stream stream) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 6d29e9f70c..1125863e72 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -1,11 +1,11 @@ using Avalonia.Media; -using Avalonia.Media.Imaging; -using Avalonia.RenderHelpers; using SkiaSharp; using System; using System.Collections.Generic; using System.Linq; using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.Rendering.Utilities; namespace Avalonia.Skia { @@ -13,17 +13,22 @@ namespace Avalonia.Skia { private readonly Matrix? _postTransform; private readonly IDisposable[] _disposables; + private readonly IVisualBrushRenderer _visualBrushRenderer; private Stack maskStack = new Stack(); public SKCanvas Canvas { get; private set; } - public DrawingContextImpl(SKCanvas canvas, Matrix? postTransform = null, params IDisposable[] disposables) + public DrawingContextImpl( + SKCanvas canvas, + IVisualBrushRenderer visualBrushRenderer, + Matrix? postTransform = null, + params IDisposable[] disposables) { if (postTransform.HasValue && !postTransform.Value.IsIdentity) _postTransform = postTransform; + _visualBrushRenderer = visualBrushRenderer; _disposables = disposables; Canvas = canvas; - Canvas.Clear(); Transform = Matrix.Identity; } @@ -194,14 +199,56 @@ namespace Avalonia.Skia } var tileBrush = brush as ITileBrush; - if (tileBrush != null) + var visualBrush = brush as IVisualBrush; + var tileBrushImage = default(BitmapImpl); + + if (visualBrush != null) { - var helper = new TileBrushImplHelper(tileBrush, targetSize); - var bitmap = new BitmapImpl((int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height); + if (_visualBrushRenderer != null) + { + var intermediateSize = _visualBrushRenderer.GetRenderTargetSize(visualBrush); + + if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1) + { + var intermediate = new BitmapImpl((int)intermediateSize.Width, (int)intermediateSize.Height); + + using (var ctx = intermediate.CreateDrawingContext(_visualBrushRenderer)) + { + ctx.Clear(Colors.Transparent); + _visualBrushRenderer.RenderVisualBrush(ctx, visualBrush); + } + + rv.AddDisposable(tileBrushImage); + tileBrushImage = intermediate; + } + } + else + { + throw new NotSupportedException("No IVisualBrushRenderer was supplied to DrawingContextImpl."); + } + } + else + { + tileBrushImage = (BitmapImpl)((tileBrush as IImageBrush)?.Source?.PlatformImpl); + } + + if (tileBrush != null && tileBrushImage != null) + { + var calc = new TileBrushCalculator(tileBrush, new Size(tileBrushImage.PixelWidth, tileBrushImage.PixelHeight), targetSize); + var bitmap = new BitmapImpl((int)calc.IntermediateSize.Width, (int)calc.IntermediateSize.Height); rv.AddDisposable(bitmap); - using (var ctx = bitmap.CreateDrawingContext()) - helper.DrawIntermediate(ctx); - SKMatrix translation = SKMatrix.MakeTranslation(-(float)helper.DestinationRect.X, -(float)helper.DestinationRect.Y); + using (var context = bitmap.CreateDrawingContext(null)) + { + var rect = new Rect(0, 0, tileBrushImage.PixelWidth, tileBrushImage.PixelHeight); + + context.Clear(Colors.Transparent); + context.PushClip(calc.IntermediateClip); + context.Transform = calc.IntermediateTransform; + context.DrawImage(tileBrushImage, 1, rect, rect); + context.PopClip(); + } + + SKMatrix translation = SKMatrix.MakeTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y); SKShaderTileMode tileX = tileBrush.TileMode == TileMode.None ? SKShaderTileMode.Clamp diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index b2b5a0653f..0eacdf41ac 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -4,6 +4,7 @@ using System.Text; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Rendering; using SkiaSharp; namespace Avalonia.Skia @@ -56,7 +57,7 @@ namespace Avalonia.Skia } - public DrawingContext CreateDrawingContext() + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { var fb = _surface.Lock(); PixelFormatShim shim = null; @@ -69,15 +70,14 @@ namespace Avalonia.Skia throw new Exception("Unable to create a surface for pixel format " + fb.Format + " or pixel format translator"); var canvas = surface.Canvas; - - - + + + canvas.RestoreToCount(0); canvas.Save(); - canvas.Clear(SKColors.Red); canvas.ResetMatrix(); var scale = Matrix.CreateScale(fb.Dpi.Width / 96, fb.Dpi.Height / 96); - return new DrawingContext(new DrawingContextImpl(canvas, scale, canvas, surface, shim, fb)); + return new DrawingContextImpl(canvas, visualBrushRenderer, scale, canvas, surface, shim, fb); } } } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 6912c52515..aea1dea584 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -5,16 +5,15 @@ using System.Linq; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; using Avalonia.Platform; -using Avalonia.Rendering; using SkiaSharp; namespace Avalonia.Skia { - public partial class PlatformRenderInterface : IPlatformRenderInterface, IRendererFactory + public partial class PlatformRenderInterface : IPlatformRenderInterface { public IBitmapImpl CreateBitmap(int width, int height) { - return CreateRenderTargetBitmap(width, height); + return CreateRenderTargetBitmap(width, height, 96, 96); } public IFormattedTextImpl CreateFormattedText( @@ -67,12 +66,11 @@ namespace Avalonia.Skia } } - public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) - { - return new Renderer(root, renderLoop); - } - - public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) + public IRenderTargetBitmapImpl CreateRenderTargetBitmap( + int width, + int height, + double dpiX, + double dpiY) { if (width < 1) throw new ArgumentException("Width can't be less than 1", nameof(width)); diff --git a/src/Skia/Avalonia.Skia/SkiaPlatform.cs b/src/Skia/Avalonia.Skia/SkiaPlatform.cs index 0751d86bf3..d3083d3d33 100644 --- a/src/Skia/Avalonia.Skia/SkiaPlatform.cs +++ b/src/Skia/Avalonia.Skia/SkiaPlatform.cs @@ -27,8 +27,7 @@ namespace Avalonia.Skia { var renderInterface = new PlatformRenderInterface(); AvaloniaLocator.CurrentMutable - .Bind().ToConstant(renderInterface) - .Bind().ToConstant(renderInterface); + .Bind().ToConstant(renderInterface); } public static bool ForceSoftwareRendering diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index ffba7a11ff..e71201b949 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -54,6 +54,7 @@ + @@ -62,7 +63,6 @@ - diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 9268324808..7ac527bb1e 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -27,7 +27,7 @@ namespace Avalonia namespace Avalonia.Direct2D1 { - public class Direct2D1Platform : IPlatformRenderInterface, IRendererFactory + public class Direct2D1Platform : IPlatformRenderInterface { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); @@ -76,7 +76,6 @@ namespace Avalonia.Direct2D1 { AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) - .Bind().ToConstant(s_instance) .BindToSelf(s_d2D1Factory) .BindToSelf(s_dwfactory) .BindToSelf(s_imagingFactory) @@ -107,11 +106,6 @@ namespace Avalonia.Direct2D1 spans); } - public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) - { - return new Renderer(root, renderLoop); - } - public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { var nativeWindow = surfaces?.OfType().FirstOrDefault(); @@ -124,9 +118,20 @@ namespace Avalonia.Direct2D1 throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } - public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) + public IRenderTargetBitmapImpl CreateRenderTargetBitmap( + int width, + int height, + double dpiX, + double dpiY) { - return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Device.Factory, width, height); + return new RenderTargetBitmapImpl( + s_imagingFactory, + s_d2D1Factory, + s_dwfactory, + width, + height, + dpiX, + dpiY); } public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = null) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 332670681a..535ca900c2 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -6,6 +6,8 @@ using System.Collections; using System.Collections.Generic; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.RenderHelpers; +using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; using SharpDX.Mathematics.Interop; @@ -18,30 +20,27 @@ namespace Avalonia.Direct2D1.Media /// public class DrawingContextImpl : IDrawingContextImpl, IDisposable { - /// - /// The Direct2D1 render target. - /// + private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; - - /// - /// The DirectWrite factory. - /// + private readonly SharpDX.DXGI.SwapChain1 _swapChain; private SharpDX.DirectWrite.Factory _directWriteFactory; - private SharpDX.DXGI.SwapChain1 _swapChain; - /// /// Initializes a new instance of the class. /// + /// The visual brush renderer. /// The render target to draw to. /// The DirectWrite factory. /// An optional swap chain associated with this drawing context. public DrawingContextImpl( + IVisualBrushRenderer visualBrushRenderer, SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.DirectWrite.Factory directWriteFactory, SharpDX.DXGI.SwapChain1 swapChain = null) { + _visualBrushRenderer = visualBrushRenderer; _renderTarget = renderTarget; + _swapChain = swapChain; _directWriteFactory = directWriteFactory; _swapChain = swapChain; _renderTarget.BeginDraw(); @@ -72,10 +71,10 @@ namespace Avalonia.Direct2D1.Media try { _renderTarget.EndDraw(); - + _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None); } - catch (SharpDXException ex) when((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET + catch (SharpDXException ex) when ((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET { throw new RenderTargetCorruptedException(ex); } @@ -90,14 +89,38 @@ namespace Avalonia.Direct2D1.Media /// The rect in the output to draw to. public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) { - var impl = (BitmapImpl)source; - Bitmap d2d = impl.GetDirect2DBitmap(_renderTarget); - _renderTarget.DrawBitmap( - d2d, - destRect.ToSharpDX(), - (float)opacity, - BitmapInterpolationMode.Linear, - sourceRect.ToSharpDX()); + using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) + { + _renderTarget.DrawBitmap( + d2d, + destRect.ToSharpDX(), + (float)opacity, + BitmapInterpolationMode.Linear, + sourceRect.ToSharpDX()); + } + } + + /// + /// Draws a bitmap image. + /// + /// The bitmap image. + /// The opacity mask to draw with. + /// The destination rect for the opacity mask. + /// The rect in the output to draw to. + public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) + { + using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) + using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource)) + using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) + using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) + { + d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D(); + + _renderTarget.FillGeometry( + geometry, + sourceBrush, + d2dOpacityMask.PlatformBrush); + } } /// @@ -283,7 +306,7 @@ namespace Avalonia.Direct2D1.Media { ContentBounds = PrimitiveExtensions.RectangleInfinite, MaskTransform = PrimitiveExtensions.Matrix3x2Identity, - Opacity = (float) opacity, + Opacity = (float)opacity, }; var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); @@ -338,16 +361,46 @@ namespace Avalonia.Direct2D1.Media } else if (imageBrush != null) { - return new TileBrushImpl(imageBrush, _renderTarget, destinationSize); + return new ImageBrushImpl( + imageBrush, + _renderTarget, + (BitmapImpl)imageBrush.Source.PlatformImpl, + destinationSize); } else if (visualBrush != null) { - return new TileBrushImpl(visualBrush, _renderTarget, destinationSize); - } - else - { - return new SolidColorBrushImpl(null, _renderTarget); + if (_visualBrushRenderer != null) + { + var intermediateSize = _visualBrushRenderer.GetRenderTargetSize(visualBrush); + + if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1) + { + using (var intermediate = new BitmapRenderTarget( + _renderTarget, + CompatibleRenderTargetOptions.None, + intermediateSize.ToSharpDX())) + { + using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer)) + { + intermediate.Clear(null); + _visualBrushRenderer.RenderVisualBrush(ctx, visualBrush); + } + + return new ImageBrushImpl( + visualBrush, + _renderTarget, + new D2DBitmapImpl(intermediate.Bitmap), + destinationSize); + } + } + } + else + { + throw new NotSupportedException("No IVisualBrushRenderer was supplied to DrawingContextImpl."); + } } + + return new SolidColorBrushImpl(null, _renderTarget); } public void PushGeometryClip(IGeometryImpl clip) diff --git a/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs similarity index 50% rename from src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs rename to src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index b0c4a2c83b..ed3d78b4fd 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -1,51 +1,48 @@ // 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.Media; -using Avalonia.RenderHelpers; +using Avalonia.Rendering.Utilities; using SharpDX.Direct2D1; namespace Avalonia.Direct2D1.Media { - public sealed class TileBrushImpl : BrushImpl + public sealed class ImageBrushImpl : BrushImpl { - public TileBrushImpl( + public ImageBrushImpl( ITileBrush brush, SharpDX.Direct2D1.RenderTarget target, + BitmapImpl bitmap, Size targetSize) { - var helper = new TileBrushImplHelper(brush, targetSize); - if (!helper.IsValid) - return; + var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize); - using (var intermediate = new BitmapRenderTarget(target, CompatibleRenderTargetOptions.None, helper.IntermediateSize.ToSharpDX())) + if (!calc.NeedsIntermediate) { - using (var ctx = new RenderTarget(intermediate).CreateDrawingContext()) - { - intermediate.Clear(null); - helper.DrawIntermediate(ctx); - } - PlatformBrush = new BitmapBrush( target, - intermediate.Bitmap, + bitmap.GetDirect2DBitmap(target), GetBitmapBrushProperties(brush), - GetBrushProperties(brush, helper.DestinationRect)); + GetBrushProperties(brush, calc.DestinationRect)); + } + else + { + using (var intermediate = RenderIntermediate(target, bitmap, calc)) + { + PlatformBrush = new BitmapBrush( + target, + intermediate.Bitmap, + GetBitmapBrushProperties(brush), + GetBrushProperties(brush, calc.DestinationRect)); + } } } - private static BrushProperties GetBrushProperties(ITileBrush brush, Rect destinationRect) + public override void Dispose() { - var tileTransform = - brush.TileMode != TileMode.None ? - Matrix.CreateTranslation(destinationRect.X, destinationRect.Y) : - Matrix.Identity; - - return new BrushProperties - { - Opacity = (float)brush.Opacity, - Transform = tileTransform.ToDirect2D(), - }; + ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); + base.Dispose(); } private static BitmapBrushProperties GetBitmapBrushProperties(ITileBrush brush) @@ -59,6 +56,20 @@ namespace Avalonia.Direct2D1.Media }; } + private static BrushProperties GetBrushProperties(ITileBrush brush, Rect destinationRect) + { + var tileTransform = + brush.TileMode != TileMode.None ? + Matrix.CreateTranslation(destinationRect.X, destinationRect.Y) : + Matrix.Identity; + + return new BrushProperties + { + Opacity = (float)brush.Opacity, + Transform = tileTransform.ToDirect2D(), + }; + } + private static ExtendMode GetExtendModeX(TileMode tileMode) { return (tileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap; @@ -69,10 +80,28 @@ namespace Avalonia.Direct2D1.Media return (tileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap; } - public override void Dispose() + private BitmapRenderTarget RenderIntermediate( + SharpDX.Direct2D1.RenderTarget target, + BitmapImpl bitmap, + TileBrushCalculator calc) { - ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); - base.Dispose(); + var result = new BitmapRenderTarget( + target, + CompatibleRenderTargetOptions.None, + calc.IntermediateSize.ToSharpDX()); + + using (var context = new RenderTarget(result).CreateDrawingContext(null)) + { + var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); + + context.Clear(Colors.Transparent); + context.PushClip(calc.IntermediateClip); + context.Transform = calc.IntermediateTransform; + context.DrawImage(bitmap, 1, rect, rect); + context.PopClip(); + } + + return result; } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs index 59f3734649..33736b02cb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs @@ -2,35 +2,41 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX.Direct2D1; using SharpDX.WIC; +using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media { public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl { + private readonly DirectWriteFactory _dwriteFactory; private readonly WicRenderTarget _target; public RenderTargetBitmapImpl( ImagingFactory imagingFactory, Factory d2dFactory, + DirectWriteFactory dwriteFactory, int width, - int height) + int height, + double dpiX, + double dpiY) : base(imagingFactory, width, height) { var props = new RenderTargetProperties { - DpiX = 96, - DpiY = 96, + DpiX = (float)dpiX, + DpiY = (float)dpiY, }; _target = new WicRenderTarget( d2dFactory, WicImpl, props); + + _dwriteFactory = dwriteFactory; } public override void Dispose() @@ -39,7 +45,9 @@ namespace Avalonia.Direct2D1.Media base.Dispose(); } - public Avalonia.Media.DrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext(); - + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory); + } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 1554296c0f..e1fffd58d4 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -116,14 +116,9 @@ namespace Avalonia.Direct2D1.Media /// The Direct2D bitmap. public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) { - if (_direct2D == null) - { - FormatConverter converter = new FormatConverter(_factory); - converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); - _direct2D = SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter); - } - - return _direct2D; + FormatConverter converter = new FormatConverter(_factory); + converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); + return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter); } /// diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index e105f90442..b4c9b49e3f 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -2,11 +2,10 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Avalonia.Direct2D1.Media; using Avalonia.Platform; -using Avalonia.Win32.Interop; -using SharpDX; +using Avalonia.Rendering; using SharpDX.Direct2D1; -using DrawingContext = Avalonia.Media.DrawingContext; using DwFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1 @@ -48,10 +47,10 @@ namespace Avalonia.Direct2D1 /// /// Creates a drawing context for a rendering session. /// - /// An . - public DrawingContext CreateDrawingContext() + /// An . + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContext(new Media.DrawingContextImpl(_renderTarget, DirectWriteFactory)); + return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory); } public void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index afad78ecca..26be2fc259 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -1,11 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Media; using Avalonia.Platform; -using Avalonia.Win32.Interop; using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; @@ -14,6 +8,8 @@ using AlphaMode = SharpDX.Direct2D1.AlphaMode; using Device = SharpDX.Direct2D1.Device; using Factory = SharpDX.Direct2D1.Factory; using Factory2 = SharpDX.DXGI.Factory2; +using Avalonia.Rendering; +using Avalonia.Direct2D1.Media; namespace Avalonia.Direct2D1 { @@ -56,8 +52,8 @@ namespace Avalonia.Direct2D1 /// /// Creates a drawing context for a rendering session. /// - /// An . - public DrawingContext CreateDrawingContext() + /// An . + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { var size = GetWindowSize(); var dpi = GetWindowDpi(); @@ -69,7 +65,11 @@ namespace Avalonia.Direct2D1 CreateSwapChain(); } - return new DrawingContext(new Media.DrawingContextImpl(_deviceContext, DirectWriteFactory, _swapChain)); + return new DrawingContextImpl( + visualBrushRenderer, + _deviceContext, + DirectWriteFactory, + _swapChain); } public void Dispose() diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index e668fd964a..b6cfb03221 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -35,7 +35,7 @@ namespace Avalonia namespace Avalonia.Win32 { - partial class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader + partial class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IRendererFactory { private static readonly Win32Platform s_instance = new Win32Platform(); private static uint _uiThread; @@ -70,6 +70,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop(60)) + .Bind().ToConstant(s_instance) .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); @@ -197,5 +198,10 @@ namespace Avalonia.Win32 { return new PopupImpl(); } + + public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) + { + return new ImmediateRenderer(root); + } } } diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs deleted file mode 100644 index 6d1f321b5f..0000000000 --- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs +++ /dev/null @@ -1,509 +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 Avalonia.Controls; -using Avalonia.Controls.Presenters; -using Avalonia.Layout; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.UnitTests; -using Moq; -using System; -using System.Collections.Generic; -using System.IO; -using Xunit; - -namespace Avalonia.Input.UnitTests -{ - public class InputElement_HitTesting - { - [Fact] - public void InputHitTest_Should_Find_Control_At_Point() - { - using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) - { - var container = new Decorator - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); - - var result = container.InputHitTest(new Point(100, 100)); - - Assert.Equal(container.Child, result); - } - } - - [Fact] - public void InputHitTest_Should_Not_Find_Control_Outside_Point() - { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) - { - var container = new Decorator - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); - - var result = container.InputHitTest(new Point(10, 10)); - - Assert.Equal(container, result); - } - } - - [Fact] - public void InputHitTest_Should_Find_Top_Control_At_Point() - { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) - { - var container = new Panel - { - Width = 200, - Height = 200, - Children = new Controls.Controls - { - new Border - { - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 50, - Height = 50, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - } - }; - - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); - - var result = container.InputHitTest(new Point(100, 100)); - - Assert.Equal(container.Children[1], result); - } - } - - [Fact] - public void InputHitTest_Should_Find_Top_Control_At_Point_With_ZOrder() - { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) - { - var container = new Panel - { - Width = 200, - Height = 200, - Children = new Controls.Controls - { - new Border - { - Width = 100, - Height = 100, - ZIndex = 1, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 50, - Height = 50, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - } - }; - - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); - - var result = container.InputHitTest(new Point(100, 100)); - - Assert.Equal(container.Children[0], result); - } - } - - [Fact] - public void InputHitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() - { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) - { - Border target; - var container = new Panel - { - Width = 200, - Height = 200, - ClipToBounds = false, - Children = new Controls.Controls - { - new Border - { - Width = 100, - Height = 100, - ZIndex = 1, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Child = target = new Border - { - Width = 50, - Height = 50, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - RenderTransform = new TranslateTransform(110, 110), - } - }, - } - }; - - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); - - var result = container.InputHitTest(new Point(120, 120)); - - Assert.Equal(target, result); - } - } - - [Fact] - public void InputHitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() - { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) - { - Border target; - - var container = new Panel - { - Width = 100, - Height = 200, - Children = new Controls.Controls - { - new Panel() - { - Width = 100, - Height = 100, - Margin = new Thickness(0, 100, 0, 0), - ClipToBounds = true, - Children = new Controls.Controls - { - (target = new Border() - { - Width = 100, - Height = 100, - Margin = new Thickness(0, -100, 0, 0) - }) - } - } - } - }; - - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); - - var result = container.InputHitTest(new Point(50, 50)); - - Assert.NotEqual(target, result); - Assert.Equal(container, result); - } - } - - [Fact] - public void InputHitTest_Should_Not_Find_Control_Outside_Scroll_ViewPort() - { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) - { - Border target; - Border item1; - Border item2; - ScrollContentPresenter scroll; - - var container = new Panel - { - Width = 100, - Height = 200, - Children = new Controls.Controls - { - (target = new Border() - { - Width = 100, - Height = 100 - }), - new Border() - { - Width = 100, - Height = 100, - Margin = new Thickness(0, 100, 0, 0), - Child = scroll = new ScrollContentPresenter() - { - Content = new StackPanel() - { - Children = new Controls.Controls - { - (item1 = new Border() - { - Width = 100, - Height = 100, - }), - (item2 = new Border() - { - Width = 100, - Height = 100, - }), - } - } - } - } - } - }; - - scroll.UpdateChild(); - - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); - - var result = container.InputHitTest(new Point(50, 150)); - - Assert.Equal(item1, result); - - result = container.InputHitTest(new Point(50, 50)); - - Assert.Equal(target, result); - - scroll.Offset = new Vector(0, 100); - - //we don't have setup LayoutManager so we will make it manually - scroll.Parent.InvalidateArrange(); - container.InvalidateArrange(); - - container.Arrange(new Rect(container.DesiredSize)); - context.Render(container); - - result = container.InputHitTest(new Point(50, 150)); - - Assert.Equal(item2, result); - - result = container.InputHitTest(new Point(50, 50)); - - Assert.NotEqual(item1, result); - Assert.Equal(target, result); - } - } - - class MockRenderInterface : IPlatformRenderInterface - { - public IFormattedTextImpl CreateFormattedText(string text, Typeface typeface, TextAlignment textAlignment, TextWrapping wrapping, Size constraint, IReadOnlyList spans) - { - throw new NotImplementedException(); - } - - public IRenderTarget CreateRenderTarget(IEnumerable surfaces) - { - throw new NotImplementedException(); - } - - public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) - { - throw new NotImplementedException(); - } - - public IStreamGeometryImpl CreateStreamGeometry() - { - return new MockStreamGeometry(); - } - - public IBitmapImpl LoadBitmap(Stream stream) - { - throw new NotImplementedException(); - } - - public IBitmapImpl LoadBitmap(string fileName) - { - throw new NotImplementedException(); - } - - public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) - { - throw new NotImplementedException(); - } - - public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt) - { - throw new NotImplementedException(); - } - - class MockStreamGeometry : Avalonia.Platform.IStreamGeometryImpl - { - private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); - public Rect Bounds - { - get - { - throw new NotImplementedException(); - } - } - - public Matrix Transform - { - get - { - throw new NotImplementedException(); - } - - set - { - throw new NotImplementedException(); - } - } - - public IStreamGeometryImpl Clone() - { - return this; - } - - public bool FillContains(Point point) - { - return _impl.FillContains(point); - } - - public Rect GetRenderBounds(double strokeThickness) - { - throw new NotImplementedException(); - } - - public IGeometryImpl Intersect(IGeometryImpl geometry) - { - throw new NotImplementedException(); - } - - public IStreamGeometryContextImpl Open() - { - return _impl; - } - - public bool StrokeContains(Pen pen, Point point) - { - throw new NotImplementedException(); - } - - public IGeometryImpl WithTransform(Matrix transform) - { - throw new NotImplementedException(); - } - - class MockStreamGeometryContext : IStreamGeometryContextImpl - { - private List points = new List(); - public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) - { - throw new NotImplementedException(); - } - - public void BeginFigure(Point startPoint, bool isFilled) - { - points.Add(startPoint); - } - - public void CubicBezierTo(Point point1, Point point2, Point point3) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - } - - public void EndFigure(bool isClosed) - { - } - - public void LineTo(Point point) - { - points.Add(point); - } - - public void QuadraticBezierTo(Point control, Point endPoint) - { - throw new NotImplementedException(); - } - - public void SetFillRule(FillRule fillRule) - { - } - - public bool FillContains(Point point) - { - // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html - // to determine if the point is in the geometry (since it will always be convex in this situation) - for (int i = 0; i < points.Count; i++) - { - var a = points[i]; - var b = points[(i + 1) % points.Count]; - var c = points[(i + 2) % points.Count]; - - Vector v0 = c - a; - Vector v1 = b - a; - Vector v2 = point - a; - - var dot00 = v0 * v0; - var dot01 = v0 * v1; - var dot02 = v0 * v2; - var dot11 = v1 * v1; - var dot12 = v1 * v2; - - - var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); - var u = (dot11 * dot02 - dot01 * dot12) * invDenom; - var v = (dot00 * dot12 - dot01 * dot02) * invDenom; - if ((u >= 0) && (v >= 0) && (u + v < 1)) return true; - } - return false; - } - } - } - } - } -} diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index 1fd5d1eda6..2e6daa8554 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -79,7 +79,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media var fb = new Framebuffer(fmt, 80, 80); var r = Avalonia.AvaloniaLocator.Current.GetService(); using (var target = r.CreateRenderTarget(new object[] { fb })) - using (var ctx = target.CreateDrawingContext()) + using (var ctx = target.CreateDrawingContext(null)) { ctx.PushOpacity(0.8); ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100)); @@ -91,13 +91,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media fb.Deallocate(); using (var rtb = new RenderTargetBitmap(100, 100)) { - using (var ctx = rtb.CreateDrawingContext()) + using (var ctx = rtb.CreateDrawingContext(null)) { ctx.FillRectangle(Brushes.Blue, new Rect(0, 0, 100, 100)); ctx.FillRectangle(Brushes.Pink, new Rect(0, 20, 100, 10)); var rc = new Rect(0, 0, 60, 60); - ctx.DrawImage(bmp, 1, rc, rc); + ctx.DrawImage(bmp.PlatformImpl, 1, rc, rc); } rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png")); } diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 252268b9ba..628ccb2a1f 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -1,7 +1,6 @@  net461;netcoreapp1.1 - False false @@ -31,18 +30,6 @@ - - - - - - - - - - - - diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs new file mode 100644 index 0000000000..8c6c949e07 --- /dev/null +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Avalonia.Media; +using Avalonia.Platform; +using Moq; + +namespace Avalonia.UnitTests +{ + public class MockPlatformRenderInterface : IPlatformRenderInterface + { + public IFormattedTextImpl CreateFormattedText( + string text, + Typeface typeface, + TextAlignment textAlignment, + TextWrapping wrapping, + Size constraint, + IReadOnlyList spans) + { + return Mock.Of(); + } + + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + return Mock.Of(); + } + + public IRenderTargetBitmapImpl CreateRenderTargetBitmap( + int width, + int height, + double dpiX, + double dpiY) + { + return Mock.Of(); + } + + public IStreamGeometryImpl CreateStreamGeometry() + { + return new MockStreamGeometryImpl(); + } + + public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = default(PixelFormat?)) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmap(Stream stream) + { + return Mock.Of(); + } + + public IBitmapImpl LoadBitmap(string fileName) + { + return Mock.Of(); + } + + public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) + { + throw new NotImplementedException(); + } + } +} diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs new file mode 100644 index 0000000000..4ef864d843 --- /dev/null +++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.UnitTests +{ + public class MockStreamGeometryImpl : IStreamGeometryImpl + { + private MockStreamGeometryContext _context; + + public MockStreamGeometryImpl() + { + Transform = Matrix.Identity; + _context = new MockStreamGeometryContext(); + } + + public MockStreamGeometryImpl(Matrix transform) + { + Transform = transform; + _context = new MockStreamGeometryContext(); + } + + private MockStreamGeometryImpl(Matrix transform, MockStreamGeometryContext context) + { + Transform = transform; + _context = context; + } + + public Rect Bounds => _context.CalculateBounds(); + + public Matrix Transform { get; } + + public IStreamGeometryImpl Clone() + { + return this; + } + + public bool FillContains(Point point) + { + return _context.FillContains(point); + } + + public bool StrokeContains(Pen pen, Point point) + { + return false; + } + + public Rect GetRenderBounds(double strokeThickness) => Bounds; + + public IGeometryImpl Intersect(IGeometryImpl geometry) + { + return new MockStreamGeometryImpl(Transform); + } + + public IStreamGeometryContextImpl Open() + { + return _context; + } + + public IGeometryImpl WithTransform(Matrix transform) + { + return new MockStreamGeometryImpl(transform, _context); + } + + class MockStreamGeometryContext : IStreamGeometryContextImpl + { + private List points = new List(); + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) + { + } + + public void BeginFigure(Point startPoint, bool isFilled) + { + points.Add(startPoint); + } + + public Rect CalculateBounds() + { + var left = double.MaxValue; + var right = double.MinValue; + var top = double.MaxValue; + var bottom = double.MinValue; + + foreach (var p in points) + { + left = Math.Min(p.X, left); + right = Math.Max(p.X, right); + top = Math.Min(p.Y, top); + bottom = Math.Max(p.Y, bottom); + } + + return new Rect(new Point(left, top), new Point(right, bottom)); + } + + public void CubicBezierTo(Point point1, Point point2, Point point3) + { + } + + public void Dispose() + { + } + + public void EndFigure(bool isClosed) + { + } + + public void LineTo(Point point) + { + points.Add(point); + } + + public void QuadraticBezierTo(Point control, Point endPoint) + { + throw new NotImplementedException(); + } + + public void SetFillRule(FillRule fillRule) + { + } + + public bool FillContains(Point point) + { + // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html + // to determine if the point is in the geometry (since it will always be convex in this situation) + for (int i = 0; i < points.Count; i++) + { + var a = points[i]; + var b = points[(i + 1) % points.Count]; + var c = points[(i + 2) % points.Count]; + + Vector v0 = c - a; + Vector v1 = b - a; + Vector v2 = point - a; + + var dot00 = v0 * v0; + var dot01 = v0 * v1; + var dot02 = v0 * v2; + var dot11 = v1 * v1; + var dot12 = v1 * v2; + + + var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + var u = (dot11 * dot02 - dot01 * dot12) * invDenom; + var v = (dot00 * dot12 - dot01 * dot02) * invDenom; + if ((u >= 0) && (v >= 0) && (u + v < 1)) return true; + } + return false; + } + } + } +} diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 8c887343ee..0cd8d4295b 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -23,7 +23,7 @@ namespace Avalonia.UnitTests assetLoader: new AssetLoader(), layoutManager: new LayoutManager(), platform: new AppBuilder().RuntimePlatform, - renderer: Mock.Of(), + renderer: (_, __) => Mock.Of(), renderInterface: CreateRenderInterfaceMock(), renderLoop: Mock.Of(), standardCursorFactory: Mock.Of(), @@ -65,7 +65,7 @@ namespace Avalonia.UnitTests Func keyboardDevice = null, ILayoutManager layoutManager = null, IRuntimePlatform platform = null, - IRenderer renderer = null, + Func renderer = null, IPlatformRenderInterface renderInterface = null, IRenderLoop renderLoop = null, IScheduler scheduler = null, @@ -98,7 +98,7 @@ namespace Avalonia.UnitTests public Func KeyboardDevice { get; } public ILayoutManager LayoutManager { get; } public IRuntimePlatform Platform { get; } - public IRenderer Renderer { get; } + public Func Renderer { get; } public IPlatformRenderInterface RenderInterface { get; } public IRenderLoop RenderLoop { get; } public IScheduler Scheduler { get; } @@ -115,7 +115,7 @@ namespace Avalonia.UnitTests Func keyboardDevice = null, ILayoutManager layoutManager = null, IRuntimePlatform platform = null, - IRenderer renderer = null, + Func renderer = null, IPlatformRenderInterface renderInterface = null, IRenderLoop renderLoop = null, IScheduler scheduler = null, diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index a9a1739ee6..c5d533486b 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -51,7 +51,7 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.KeyboardDevice?.Invoke()) .Bind().ToConstant(Services.LayoutManager) .Bind().ToConstant(Services.Platform) - .Bind().ToConstant(Services.Renderer) + .Bind().ToConstant(new RendererFactory(Services.Renderer)) .Bind().ToConstant(Services.RenderInterface) .Bind().ToConstant(Services.RenderLoop) .Bind().ToConstant(Services.ThreadingInterface) @@ -67,5 +67,20 @@ namespace Avalonia.UnitTests Styles.AddRange(styles); } } + + private class RendererFactory : IRendererFactory + { + Func _func; + + public RendererFactory(Func func) + { + _func = func; + } + + public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) + { + return _func?.Invoke(root, renderLoop); + } + } } } diff --git a/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs b/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs index a95a6fe7f2..bcb5250be6 100644 --- a/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs +++ b/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs @@ -163,7 +163,7 @@ namespace Avalonia.Visuals.UnitTests var ctx = CreateDrawingContext(); control.Measure(Size.Infinity); control.Arrange(new Rect(control.DesiredSize)); - ctx.Render(control); + ImmediateRenderer.Render(control, ctx); } private DrawingContext CreateDrawingContext() diff --git a/tests/Avalonia.Visuals.UnitTests/TestRoot.cs b/tests/Avalonia.Visuals.UnitTests/TestRoot.cs deleted file mode 100644 index bdcbae59ce..0000000000 --- a/tests/Avalonia.Visuals.UnitTests/TestRoot.cs +++ /dev/null @@ -1,39 +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 Avalonia.Platform; -using Avalonia.Rendering; - -namespace Avalonia.Visuals.UnitTests -{ - public class TestRoot : TestVisual, IRenderRoot - { - public Size ClientSize { get; } - - public IRenderTarget CreateRenderTarget() - { - throw new NotImplementedException(); - } - - public void Invalidate(Rect rect) - { - throw new NotImplementedException(); - } - - public IRenderer Renderer - { - get { throw new NotImplementedException(); } - } - - public Point PointToClient(Point p) - { - throw new NotImplementedException(); - } - - public Point PointToScreen(Point p) - { - throw new NotImplementedException(); - } - } -} diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs index 8daaacd051..e80d4f48f9 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs @@ -5,7 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using Avalonia.Controls; +using Avalonia.Rendering; +using Avalonia.UnitTests; using Avalonia.VisualTree; +using Moq; using Xunit; namespace Avalonia.Visuals.UnitTests @@ -66,14 +69,25 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Adding_Children_Should_Fire_OnAttachedToVisualTree() { - var child2 = new TestVisual(); - var child1 = new TestVisual { Child = child2 }; + var child2 = new Decorator(); + var child1 = new Decorator { Child = child2 }; var root = new TestRoot(); var called1 = false; var called2 = false; - child1.AttachedToVisualTree += (s, e) => called1 = true; - child2.AttachedToVisualTree += (s, e) => called2 = true; + child1.AttachedToVisualTree += (s, e) => + { + Assert.Equal(e.Parent, root); + Assert.Equal(e.Root, root); + called1 = true; + }; + + child2.AttachedToVisualTree += (s, e) => + { + Assert.Equal(e.Parent, root); + Assert.Equal(e.Root, root); + called2 = true; + }; root.Child = child1; @@ -84,31 +98,100 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Removing_Children_Should_Fire_OnDetachedFromVisualTree() { - var child2 = new TestVisual(); - var child1 = new TestVisual { Child = child2 }; + var child2 = new Decorator(); + var child1 = new Decorator { Child = child2 }; var root = new TestRoot(); var called1 = false; var called2 = false; root.Child = child1; - child1.DetachedFromVisualTree += (s, e) => called1 = true; - child2.DetachedFromVisualTree += (s, e) => called2 = true; + + child1.DetachedFromVisualTree += (s, e) => + { + Assert.Equal(e.Parent, root); + Assert.Equal(e.Root, root); + called1 = true; + }; + + child2.DetachedFromVisualTree += (s, e) => + { + Assert.Equal(e.Parent, root); + Assert.Equal(e.Root, root); + called2 = true; + }; + root.Child = null; Assert.True(called1); Assert.True(called2); } + [Fact] + public void Root_Should_Retun_Self_As_VisualRoot() + { + var root = new TestRoot(); + + Assert.Same(root, ((IVisual)root).VisualRoot); + } + + [Fact] + public void Descendents_Should_RetunVisualRoot() + { + var root = new TestRoot(); + var child1 = new Decorator(); + var child2 = new Decorator(); + + root.Child = child1; + child1.Child = child2; + + Assert.Same(root, ((IVisual)child1).VisualRoot); + Assert.Same(root, ((IVisual)child2).VisualRoot); + } + + [Fact] + public void Attaching_To_Visual_Tree_Should_Invalidate_Visual() + { + var renderer = new Mock(); + + using (UnitTestApplication.Start(new TestServices(renderer: (root, loop) => renderer.Object))) + { + var child = new Decorator(); + var root = new TestRoot(); + + root.Child = child; + + renderer.Verify(x => x.AddDirty(child)); + } + } + + [Fact] + public void Detaching_From_Visual_Tree_Should_Invalidate_Visual() + { + var renderer = new Mock(); + + using (UnitTestApplication.Start(new TestServices(renderer: (root, loop) => renderer.Object))) + { + var child = new Decorator(); + var root = new TestRoot(); + + root.Child = child; + renderer.ResetCalls(); + root.Child = null; + + renderer.Verify(x => x.AddDirty(child)); + } + } + [Fact] public void Adding_Already_Parented_Control_Should_Throw() { var root1 = new TestRoot(); var root2 = new TestRoot(); - var child = new TestVisual(); + var child = new Canvas(); - root1.AddChild(child); + root1.Child = child; - Assert.Throws(() => root2.AddChild(child)); + Assert.Throws(() => root2.Child = child); Assert.Equal(0, root2.GetVisualChildren().Count()); } } diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs index 925b21b691..ea3a1cdd78 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/BoundsTrackerTests.cs @@ -44,7 +44,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree tree.Measure(Size.Infinity); tree.Arrange(new Rect(0, 0, 100, 100)); - context.Render(tree); + ImmediateRenderer.Render(tree, context); var track = target.Track(control); var results = new List(); diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 57399f3df0..5fcf1cf1f2 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -24,7 +24,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } - public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height, double dpiX, double dpiY) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs index 7fb5d7c966..2ca5b8335a 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs @@ -12,6 +12,8 @@ using Avalonia.UnitTests; using Avalonia.VisualTree; using Moq; using Xunit; +using System; +using Avalonia.Controls.Shapes; namespace Avalonia.Visuals.UnitTests.VisualTree { @@ -20,9 +22,9 @@ namespace Avalonia.Visuals.UnitTests.VisualTree [Fact] public void GetVisualsAt_Should_Find_Controls_At_Point() { - using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + using (TestApplication()) { - var container = new Decorator + var container = new TestRoot { Width = 200, Height = 200, @@ -30,6 +32,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree { Width = 100, Height = 100, + Background = Brushes.Red, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center } @@ -38,21 +41,46 @@ namespace Avalonia.Visuals.UnitTests.VisualTree container.Measure(Size.Infinity); container.Arrange(new Rect(container.DesiredSize)); - var context = new DrawingContext(Mock.Of()); - context.Render(container); + var result = container.GetVisualsAt(new Point(100, 100)); + + Assert.Equal(new[] { container.Child }, result); + } + } + + [Fact] + public void GetVisualsAt_Should_Not_Find_Empty_Controls_At_Point() + { + using (TestApplication()) + { + var container = new TestRoot + { + Width = 200, + Height = 200, + Child = new Border + { + Width = 100, + Height = 100, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + }; + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); var result = container.GetVisualsAt(new Point(100, 100)); - Assert.Equal(new[] { container.Child, container }, result); + Assert.Empty(result); } } [Fact] public void GetVisualsAt_Should_Not_Find_Invisible_Controls_At_Point() { - using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + using (TestApplication()) { - var container = new Decorator + Border visible; + var container = new TestRoot { Width = 200, Height = 200, @@ -60,11 +88,13 @@ namespace Avalonia.Visuals.UnitTests.VisualTree { Width = 100, Height = 100, + Background = Brushes.Red, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, IsVisible = false, - Child = new Border + Child = visible = new Border { + Background = Brushes.Red, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, } @@ -74,21 +104,18 @@ namespace Avalonia.Visuals.UnitTests.VisualTree container.Measure(Size.Infinity); container.Arrange(new Rect(container.DesiredSize)); - var context = new DrawingContext(Mock.Of()); - context.Render(container); - var result = container.GetVisualsAt(new Point(100, 100)); - Assert.Equal(new[] { container }, result); + Assert.Empty(result); } } [Fact] public void GetVisualsAt_Should_Not_Find_Control_Outside_Point() { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + using (TestApplication()) { - var container = new Decorator + var container = new TestRoot { Width = 200, Height = 200, @@ -96,6 +123,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree { Width = 100, Height = 100, + Background = Brushes.Red, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center } @@ -104,142 +132,150 @@ namespace Avalonia.Visuals.UnitTests.VisualTree container.Measure(Size.Infinity); container.Arrange(new Rect(container.DesiredSize)); - var context = new DrawingContext(Mock.Of()); - context.Render(container); - var result = container.GetVisualsAt(new Point(10, 10)); - Assert.Equal(new[] { container }, result); + Assert.Empty(result); } } [Fact] public void GetVisualsAt_Should_Return_Top_Controls_First() { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + using (TestApplication()) { - var container = new Panel + Panel container; + var root = new TestRoot { - Width = 200, - Height = 200, - Children = new Controls.Controls + Child = container = new Panel { - new Border + Width = 200, + Height = 200, + Children = new Controls.Controls { - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 50, - Height = 50, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center + new Border + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }, + new Border + { + Width = 50, + Height = 50, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } } } }; - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); + root.Measure(Size.Infinity); + root.Arrange(new Rect(container.DesiredSize)); var result = container.GetVisualsAt(new Point(100, 100)); - Assert.Equal(new[] { container.Children[1], container.Children[0], container }, result); + Assert.Equal(new[] { container.Children[1], container.Children[0] }, result); } } [Fact] public void GetVisualsAt_Should_Return_Top_Controls_First_With_ZIndex() { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + using (TestApplication()) { - var container = new Panel + Panel container; + var root = new TestRoot { - Width = 200, - Height = 200, - Children = new Controls.Controls + Child = container = new Panel { - new Border - { - Width = 100, - Height = 100, - ZIndex = 1, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 50, - Height = 50, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border + Width = 200, + Height = 200, + Children = new Controls.Controls { - Width = 75, - Height = 75, - ZIndex = 2, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center + new Border + { + Width = 100, + Height = 100, + ZIndex = 1, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }, + new Border + { + Width = 50, + Height = 50, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }, + new Border + { + Width = 75, + Height = 75, + ZIndex = 2, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } } } }; - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); + root.Measure(Size.Infinity); + root.Arrange(new Rect(container.DesiredSize)); var result = container.GetVisualsAt(new Point(100, 100)); - Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1], container }, result); + Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1] }, result); } } [Fact] public void GetVisualsAt_Should_Find_Control_Translated_Outside_Parent_Bounds() { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + using (TestApplication()) { Border target; - var container = new Panel + Panel container; + var root = new TestRoot { - Width = 200, - Height = 200, - ClipToBounds = false, - Children = new Controls.Controls + Child = container = new Panel { - new Border + Width = 200, + Height = 200, + Background = Brushes.Red, + ClipToBounds = false, + Children = new Controls.Controls { - Width = 100, - Height = 100, - ZIndex = 1, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Child = target = new Border + new Border { - Width = 50, - Height = 50, + Width = 100, + Height = 100, + ZIndex = 1, + Background = Brushes.Red, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, - RenderTransform = new TranslateTransform(110, 110), - } - }, + Child = target = new Border + { + Width = 50, + Height = 50, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + RenderTransform = new TranslateTransform(110, 110), + } + }, + } } }; container.Measure(Size.Infinity); container.Arrange(new Rect(container.DesiredSize)); - var context = new DrawingContext(Mock.Of()); - context.Render(container); - var result = container.GetVisualsAt(new Point(120, 120)); Assert.Equal(new IVisual[] { target, container }, result); @@ -249,40 +285,43 @@ namespace Avalonia.Visuals.UnitTests.VisualTree [Fact] public void GetVisualsAt_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + using (TestApplication()) { Border target; - - var container = new Panel + Panel container; + var root = new TestRoot { - Width = 100, - Height = 200, - Children = new Controls.Controls + Child = container = new Panel { - new Panel() + Width = 100, + Height = 200, + Background = Brushes.Red, + Children = new Controls.Controls { - Width = 100, - Height = 100, - Margin = new Thickness(0, 100, 0, 0), - ClipToBounds = true, - Children = new Controls.Controls + new Panel() { - (target = new Border() + Width = 100, + Height = 100, + Background = Brushes.Red, + Margin = new Thickness(0, 100, 0, 0), + ClipToBounds = true, + Children = new Controls.Controls { - Width = 100, - Height = 100, - Margin = new Thickness(0, -100, 0, 0) - }) + (target = new Border() + { + Width = 100, + Height = 100, + Background = Brushes.Red, + Margin = new Thickness(0, -100, 0, 0) + }) + } } } } }; - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); + root.Measure(Size.Infinity); + root.Arrange(new Rect(container.DesiredSize)); var result = container.GetVisualsAt(new Point(50, 50)); @@ -293,45 +332,57 @@ namespace Avalonia.Visuals.UnitTests.VisualTree [Fact] public void GetVisualsAt_Should_Not_Find_Control_Outside_Scroll_Viewport() { - using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + using (TestApplication()) { Border target; Border item1; Border item2; ScrollContentPresenter scroll; - - var container = new Panel + Panel container; + var root = new TestRoot { - Width = 100, - Height = 200, - Children = new Controls.Controls + Child = container = new Panel { - (target = new Border() - { - Width = 100, - Height = 100 - }), - new Border() + Width = 100, + Height = 200, + Background = Brushes.Red, + Children = new Controls.Controls { - Width = 100, - Height = 100, - Margin = new Thickness(0, 100, 0, 0), - Child = scroll = new ScrollContentPresenter() + (target = new Border() + { + Name = "b1", + Width = 100, + Height = 100, + Background = Brushes.Red, + }), + new Border() { - Content = new StackPanel() + Name = "b2", + Width = 100, + Height = 100, + Background = Brushes.Red, + Margin = new Thickness(0, 100, 0, 0), + Child = scroll = new ScrollContentPresenter() { - Children = new Controls.Controls + Content = new StackPanel() { - (item1 = new Border() + Children = new Controls.Controls { - Width = 100, - Height = 100, - }), - (item2 = new Border() - { - Width = 100, - Height = 100, - }), + (item1 = new Border() + { + Name = "b3", + Width = 100, + Height = 100, + Background = Brushes.Red, + }), + (item2 = new Border() + { + Name = "b4", + Width = 100, + Height = 100, + Background = Brushes.Red, + }), + } } } } @@ -341,11 +392,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree scroll.UpdateChild(); - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - context.Render(container); + root.Measure(Size.Infinity); + root.Arrange(new Rect(container.DesiredSize)); var result = container.GetVisualsAt(new Point(50, 150)).First(); @@ -357,22 +405,96 @@ namespace Avalonia.Visuals.UnitTests.VisualTree scroll.Offset = new Vector(0, 100); - //we don't have setup LayoutManager so we will make it manually + // We don't have LayoutManager set up so do the layout pass manually. scroll.Parent.InvalidateArrange(); container.InvalidateArrange(); - container.Arrange(new Rect(container.DesiredSize)); - context.Render(container); result = container.GetVisualsAt(new Point(50, 150)).First(); - Assert.Equal(item2, result); result = container.GetVisualsAt(new Point(50, 50)).First(); - - Assert.NotEqual(item1, result); Assert.Equal(target, result); } } + + [Fact] + public void GetVisualsAt_Should_Not_Find_Path_When_Outside_Fill() + { + using (TestApplication()) + { + Path path; + var container = new TestRoot + { + Width = 200, + Height = 200, + Child = path = new Path + { + Width = 200, + Height = 200, + Fill = Brushes.Red, + Data = StreamGeometry.Parse("M100,0 L0,100 100,100") + } + }; + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); + + var context = new DrawingContext(Mock.Of()); + + var result = container.GetVisualsAt(new Point(100, 100)); + Assert.Equal(new[] { path }, result); + + result = container.GetVisualsAt(new Point(10, 10)); + Assert.Empty(result); + } + } + + [Fact] + public void GetVisualsAt_Should_Respect_Geometry_Clip() + { + using (TestApplication()) + { + Border border; + Canvas canvas; + var container = new TestRoot + { + Width = 400, + Height = 400, + Child = border = new Border + { + Background = Brushes.Red, + Clip = StreamGeometry.Parse("M100,0 L0,100 100,100"), + Width = 200, + Height = 200, + Child = canvas = new Canvas + { + Background = Brushes.Yellow, + Margin = new Thickness(10), + } + } + }; + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); + Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds); + + var context = new DrawingContext(Mock.Of()); + + var result = container.GetVisualsAt(new Point(200, 200)); + Assert.Equal(new IVisual[] { canvas, border }, result); + + result = container.GetVisualsAt(new Point(110, 110)); + Assert.Empty(result); + } + } + + private IDisposable TestApplication() + { + return UnitTestApplication.Start( + new TestServices( + renderInterface: new MockPlatformRenderInterface(), + renderer: (root, loop) => new ImmediateRenderer(root))); + } } } From cc48e6322d4e18d80bb85d2a63e8f6e828ac21e0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Apr 2017 21:17:05 +0200 Subject: [PATCH 2/8] Removed AvaloniaDisposable. No longer used. --- src/Avalonia.Base/AvaloniaDisposable.cs | 41 ------------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/Avalonia.Base/AvaloniaDisposable.cs diff --git a/src/Avalonia.Base/AvaloniaDisposable.cs b/src/Avalonia.Base/AvaloniaDisposable.cs deleted file mode 100644 index 9e43a65f26..0000000000 --- a/src/Avalonia.Base/AvaloniaDisposable.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Platform; - -namespace Avalonia -{ - public abstract class AvaloniaDisposable : IDisposable - { -#if DEBUG_DISPOSE - public string DisposedAt { get; private set; } -#endif - - - public bool IsDisposed { get; private set; } - - public void Dispose() - { - IsDisposed = true; -#if DEBUG_DISPOSE - DisposedAt = AvaloniaLocator.Current.GetService().GetStackTrace(); -#endif - DoDispose(); - } - - protected void CheckDisposed() - { - if (IsDisposed) - throw new ObjectDisposedException(GetType().FullName -#if DEBUG_DISPOSE - , "Disposed at: \n" + DisposedAt -#endif - - ); - } - - protected abstract void DoDispose(); - } -} From 3082bda4988e93edfec93d946c371be92bf4746e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Apr 2017 21:24:22 +0200 Subject: [PATCH 3/8] Pass resized message to renderer. --- src/Avalonia.Controls/WindowBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 18cc9a3419..21c248db0c 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -210,8 +210,7 @@ namespace Avalonia.Controls } ClientSize = clientSize; LayoutManager.Instance.ExecuteLayoutPass(); - PlatformImpl.Invalidate(new Rect(clientSize)); - + Renderer?.Resized(clientSize); } /// From 0c6a3f435d4d6635f048f394caef8264937417c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 20 Apr 2017 16:48:17 +0200 Subject: [PATCH 4/8] Set dotnet environment variables for ci builds --- .travis.yml | 4 ++++ appveyor.yml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5740a5ecfd..a3c02eea58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,10 @@ os: - linux - osx dist: trusty +env: + global: + - DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + - DOTNET_CLI_TELEMETRY_OPTOUT=1 mono: - latest dotnet: 1.0.1 diff --git a/appveyor.yml b/appveyor.yml index c9cd4baf80..92f1ee2c00 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,6 +5,8 @@ skip_branch_with_pr: true configuration: - Release environment: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 NUGET_API_KEY: secure: Xv89dlP2MSBZKhl1nrWSxqcDgCXB0HRhOd4SWQ+jRJ7QoLxQel5mLTipXM++J3G5 NUGET_API_URL: https://www.nuget.org/api/v2/package From 012451a4db6fecf1f65f42270d2d477ef0769c80 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 23 Apr 2017 17:16:19 +0300 Subject: [PATCH 5/8] Direct3D interop sample --- Avalonia.sln | 43 +++ .../interop/Direct3DInteropSample/App.paml | 6 + .../interop/Direct3DInteropSample/App.paml.cs | 13 + .../Direct3DInteropSample.csproj | 33 +++ .../Direct3DInteropSample/MainWindow.cs | 263 ++++++++++++++++++ .../Direct3DInteropSample/MainWindow.paml | 14 + .../MainWindowViewModel.cs | 45 +++ .../interop/Direct3DInteropSample/MiniCube.fx | 47 ++++ .../interop/Direct3DInteropSample/Program.cs | 17 ++ src/Avalonia.Controls/TopLevel.cs | 2 +- 10 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 samples/interop/Direct3DInteropSample/App.paml create mode 100644 samples/interop/Direct3DInteropSample/App.paml.cs create mode 100644 samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj create mode 100644 samples/interop/Direct3DInteropSample/MainWindow.cs create mode 100644 samples/interop/Direct3DInteropSample/MainWindow.paml create mode 100644 samples/interop/Direct3DInteropSample/MainWindowViewModel.cs create mode 100644 samples/interop/Direct3DInteropSample/MiniCube.fx create mode 100644 samples/interop/Direct3DInteropSample/Program.cs diff --git a/Avalonia.sln b/Avalonia.sln index cc166bc495..f12af02236 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -189,6 +189,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer", "src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj", "{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 @@ -2547,6 +2549,46 @@ Global {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Mono.Build.0 = Release|Any CPU {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|x86.ActiveCfg = Release|Any CPU {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|x86.Build.0 = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|iPhone.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|Mono.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|Mono.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|x86.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.AppStore|x86.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|iPhone.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Mono.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Mono.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|x86.ActiveCfg = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|x86.Build.0 = Debug|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Any CPU.Build.0 = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|iPhone.ActiveCfg = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|iPhone.Build.0 = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.ActiveCfg = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU + {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2606,5 +2648,6 @@ Global {F3AC8BC1-27F5-4255-9AFC-04ABFD11683A} = {74487168-7D91-487E-BF93-055F2251461E} {4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E} {854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} + {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} EndGlobalSection EndGlobal diff --git a/samples/interop/Direct3DInteropSample/App.paml b/samples/interop/Direct3DInteropSample/App.paml new file mode 100644 index 0000000000..d9630eef58 --- /dev/null +++ b/samples/interop/Direct3DInteropSample/App.paml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs new file mode 100644 index 0000000000..1b6d5fd39c --- /dev/null +++ b/samples/interop/Direct3DInteropSample/App.paml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Markup.Xaml; + +namespace Direct3DInteropSample +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj new file mode 100644 index 0000000000..7084bbf44f --- /dev/null +++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj @@ -0,0 +1,33 @@ + + + Exe + net461 + + + + + + %(Filename) + + + Designer + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs new file mode 100644 index 0000000000..c107afbe4a --- /dev/null +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Direct2D1.Media; +using Avalonia.Markup.Xaml; +using Avalonia.Platform; +using Avalonia.Rendering; +using SharpDX; +using SharpDX.D3DCompiler; +using SharpDX.Direct2D1; +using SharpDX.Direct3D; +using SharpDX.Direct3D11; +using SharpDX.DXGI; +using SharpDX.WIC; +using SharpDX.Mathematics; +using AlphaMode = SharpDX.Direct2D1.AlphaMode; +using Buffer = SharpDX.Direct3D11.Buffer; +using DeviceContext = SharpDX.Direct3D11.DeviceContext; +using Factory1 = SharpDX.DXGI.Factory1; +using InputElement = SharpDX.Direct3D11.InputElement; +using Matrix = SharpDX.Matrix; +using PixelFormat = SharpDX.Direct2D1.PixelFormat; + +namespace Direct3DInteropSample +{ + class MainWindow : Window + { + private SharpDX.Direct3D11.Device _d3dDevice; + private SharpDX.DXGI.Device _dxgiDevice; + Texture2D backBuffer = null; + RenderTargetView renderView = null; + Texture2D depthBuffer = null; + DepthStencilView depthView = null; + private readonly SwapChain _swapChain; + private SwapChainDescription _desc; + private Matrix _proj = Matrix.Identity; + private Matrix _view; + private Buffer _contantBuffer; + private SharpDX.Direct2D1.Device _d2dDevice; + private SharpDX.Direct2D1.DeviceContext _d2dContext; + private RenderTarget _d2dRenderTarget; + private MainWindowViewModel _model; + + public MainWindow() + { + _dxgiDevice = AvaloniaLocator.Current.GetService(); + _d3dDevice = _dxgiDevice.QueryInterface(); + _d2dDevice = AvaloniaLocator.Current.GetService(); + DataContext = _model = new MainWindowViewModel(); + _desc = new SwapChainDescription() + { + BufferCount = 1, + ModeDescription = + new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height, + new Rational(60, 1), Format.R8G8B8A8_UNorm), + IsWindowed = true, + OutputHandle = PlatformImpl.Handle.Handle, + SampleDescription = new SampleDescription(1, 0), + SwapEffect = SwapEffect.Discard, + Usage = Usage.RenderTargetOutput + }; + + _swapChain = new SwapChain(new Factory1(), _d3dDevice, _desc); + + _d2dContext = new SharpDX.Direct2D1.DeviceContext(_d2dDevice, DeviceContextOptions.None) + { + DotsPerInch = new Size2F(96, 96) + }; + + CreateMesh(); + _view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY); + this.GetObservable(ClientSizeProperty).Subscribe(Resize); + Resize(ClientSize); + AvaloniaXamlLoader.Load(this); + } + + + protected override void HandlePaint(Rect rect) + { + var viewProj = Matrix.Multiply(_view, _proj); + var context = _d3dDevice.ImmediateContext; + // Clear views + context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0); + context.ClearRenderTargetView(renderView, Color.White); + + var time = 50; + // Update WorldViewProj Matrix + var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) * + Matrix.RotationZ((float) _model.RotationZ) + * Matrix.Scaling((float) _model.Zoom) * viewProj; + worldViewProj.Transpose(); + context.UpdateSubresource(ref worldViewProj, _contantBuffer); + + // Draw the cube + context.Draw(36, 0); + base.HandlePaint(rect); + // Present! + _swapChain.Present(0, PresentFlags.None); + } + + + void CreateMesh() + { + var device = _d3dDevice; + // Compile Vertex and Pixel shaders + var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0"); + var vertexShader = new VertexShader(device, vertexShaderByteCode); + + var pixelShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "PS", "ps_4_0"); + var pixelShader = new PixelShader(device, pixelShaderByteCode); + + var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode); + // Layout from VertexShader input signature + var layout = new InputLayout(device, signature, new[] + { + new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), + new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) + }); + + // Instantiate Vertex buiffer from vertex data + var vertices = Buffer.Create(device, BindFlags.VertexBuffer, new[] + { + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + + new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom + new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + }); + + // Create Constant Buffer + _contantBuffer = new Buffer(device, Utilities.SizeOf(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0); + + var context = _d3dDevice.ImmediateContext; + + // Prepare All the stages + context.InputAssembler.InputLayout = layout; + context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; + context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertices, Utilities.SizeOf() * 2, 0)); + context.VertexShader.SetConstantBuffer(0, _contantBuffer); + context.VertexShader.Set(vertexShader); + context.PixelShader.Set(pixelShader); + } + + void Resize(Size size) + { + Utilities.Dispose(ref _d2dRenderTarget); + Utilities.Dispose(ref backBuffer); + Utilities.Dispose(ref renderView); + Utilities.Dispose(ref depthBuffer); + Utilities.Dispose(ref depthView); + var context = _d3dDevice.ImmediateContext; + // Resize the backbuffer + _swapChain.ResizeBuffers(_desc.BufferCount, (int)size.Width, (int)size.Height, Format.Unknown, SwapChainFlags.None); + + // Get the backbuffer from the swapchain + backBuffer = Texture2D.FromSwapChain(_swapChain, 0); + + // Renderview on the backbuffer + renderView = new RenderTargetView(_d3dDevice, backBuffer); + + // Create the depth buffer + depthBuffer = new Texture2D(_d3dDevice, new Texture2DDescription() + { + Format = Format.D32_Float_S8X24_UInt, + ArraySize = 1, + MipLevels = 1, + Width = (int)size.Width, + Height = (int)size.Height, + SampleDescription = new SampleDescription(1, 0), + Usage = ResourceUsage.Default, + BindFlags = BindFlags.DepthStencil, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None + }); + + // Create the depth buffer view + depthView = new DepthStencilView(_d3dDevice, depthBuffer); + + // Setup targets and viewport for rendering + context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f)); + context.OutputMerger.SetTargets(depthView, renderView); + + // Setup new projection matrix with correct aspect ratio + _proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f); + + using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) + { + _d2dRenderTarget = new RenderTarget(AvaloniaLocator.Current.GetService() + , dxgiBackBuffer, new RenderTargetProperties + { + DpiX = 96, + DpiY = 96, + Type = RenderTargetType.Default, + PixelFormat = new PixelFormat(Format.Unknown, AlphaMode.Premultiplied) + }); + } + + } + + class D3DRenderTarget: IRenderTarget + { + private readonly MainWindow _window; + + public D3DRenderTarget(MainWindow window) + { + _window = window; + } + public void Dispose() + { + + } + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget, + AvaloniaLocator.Current.GetService()); + } + } + + + public override IRenderTarget CreateRenderTarget() => new D3DRenderTarget(this); + } +} diff --git a/samples/interop/Direct3DInteropSample/MainWindow.paml b/samples/interop/Direct3DInteropSample/MainWindow.paml new file mode 100644 index 0000000000..0a689e8a22 --- /dev/null +++ b/samples/interop/Direct3DInteropSample/MainWindow.paml @@ -0,0 +1,14 @@ + + + + Rotation X + + Rotation Y + + Rotation Z + + Zoom + + + + \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs b/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs new file mode 100644 index 0000000000..d39a21cd07 --- /dev/null +++ b/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ReactiveUI; + +namespace Direct3DInteropSample +{ + public class MainWindowViewModel : ReactiveObject + { + private double _rotationX; + + public double RotationX + { + get { return _rotationX; } + set { this.RaiseAndSetIfChanged(ref _rotationX, value); } + } + + private double _rotationY = 1; + + public double RotationY + { + get { return _rotationY; } + set { this.RaiseAndSetIfChanged(ref _rotationY, value); } + } + + private double _rotationZ = 2; + + public double RotationZ + { + get { return _rotationZ; } + set { this.RaiseAndSetIfChanged(ref _rotationZ, value); } + } + + + private double _zoom = 1; + + public double Zoom + { + get { return _zoom; } + set { this.RaiseAndSetIfChanged(ref _zoom, value); } + } + } +} diff --git a/samples/interop/Direct3DInteropSample/MiniCube.fx b/samples/interop/Direct3DInteropSample/MiniCube.fx new file mode 100644 index 0000000000..f246421f2d --- /dev/null +++ b/samples/interop/Direct3DInteropSample/MiniCube.fx @@ -0,0 +1,47 @@ +// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +struct VS_IN +{ + float4 pos : POSITION; + float4 col : COLOR; +}; + +struct PS_IN +{ + float4 pos : SV_POSITION; + float4 col : COLOR; +}; + +float4x4 worldViewProj; + +PS_IN VS( VS_IN input ) +{ + PS_IN output = (PS_IN)0; + + output.pos = mul(input.pos, worldViewProj); + output.col = input.col; + + return output; +} + +float4 PS( PS_IN input ) : SV_Target +{ + return input.col; +} \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/Program.cs b/samples/interop/Direct3DInteropSample/Program.cs new file mode 100644 index 0000000000..7fb650a7a8 --- /dev/null +++ b/samples/interop/Direct3DInteropSample/Program.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia; + +namespace Direct3DInteropSample +{ + class Program + { + static void Main(string[] args) + { + AppBuilder.Configure().UseWin32().UseDirect2D1().Start(); + } + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 8549a98371..f7f74465fd 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -185,7 +185,7 @@ namespace Avalonia.Controls } /// - IRenderTarget IRenderRoot.CreateRenderTarget() + public virtual IRenderTarget CreateRenderTarget() { return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces); } From 3da37f7dc868dac7cd41ad95b1ad54b7f6bb78c1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 25 Apr 2017 19:51:22 +0300 Subject: [PATCH 6/8] Update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 92f1ee2c00..7f2b1bb395 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -os: Previous Visual Studio 2017 +os: Visual Studio 2017 platform: - Any CPU skip_branch_with_pr: true From 7a66752f2e86c969531c8fe9dbac8d72dec5aa2a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 25 Apr 2017 19:53:46 +0300 Subject: [PATCH 7/8] Make CreateRenderTarget virtual --- src/Avalonia.Controls/TopLevel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index f7f74465fd..f3a6ab92d0 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -184,8 +184,10 @@ namespace Avalonia.Controls get { return AvaloniaLocator.Current.GetService(); } } + IRenderTarget IRenderRoot.CreateRenderTarget() => CreateRenderTarget(); + /// - public virtual IRenderTarget CreateRenderTarget() + protected virtual IRenderTarget CreateRenderTarget() { return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces); } From db3db7e98777015d163200eaaa2a9817675eaf6b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 25 Apr 2017 22:20:30 +0300 Subject: [PATCH 8/8] Fixes for Direct3D example --- .../interop/Direct3DInteropSample/Direct3DInteropSample.csproj | 1 + samples/interop/Direct3DInteropSample/MainWindow.cs | 3 ++- samples/interop/Direct3DInteropSample/MainWindow.paml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj index 7084bbf44f..54a816b0a9 100644 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj @@ -20,6 +20,7 @@ + diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index c107afbe4a..1ff1e1938b 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -76,6 +76,7 @@ namespace Direct3DInteropSample this.GetObservable(ClientSizeProperty).Subscribe(Resize); Resize(ClientSize); AvaloniaXamlLoader.Load(this); + Background = Avalonia.Media.Brushes.Transparent; } @@ -258,6 +259,6 @@ namespace Direct3DInteropSample } - public override IRenderTarget CreateRenderTarget() => new D3DRenderTarget(this); + protected override IRenderTarget CreateRenderTarget() => new D3DRenderTarget(this); } } diff --git a/samples/interop/Direct3DInteropSample/MainWindow.paml b/samples/interop/Direct3DInteropSample/MainWindow.paml index 0a689e8a22..37c6265836 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.paml +++ b/samples/interop/Direct3DInteropSample/MainWindow.paml @@ -1,4 +1,4 @@ - + Rotation X