Browse Source

Merge pull request #1184 from AvaloniaUI/fixes/dc-render-layers

Use ID2D1BitmapRenderTarget for D2D render layers (Option 2)
pull/1202/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
e3d9df99bc
  1. 5
      samples/interop/Direct3DInteropSample/MainWindow.cs
  2. 29
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  3. 34
      src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs
  4. 159
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  5. 11
      src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs
  6. 10
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  7. 9
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  8. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  9. 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  10. 4
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  11. 28
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  12. 2
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  13. 10
      src/Windows/Avalonia.Direct2D1/ILayerFactory.cs
  14. 39
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  15. 7
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  16. 32
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  17. 47
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  18. 68
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  19. 58
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  20. 6
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  21. 22
      src/Windows/Avalonia.Direct2D1/OptionalDispose.cs
  22. 34
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  23. 40
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  24. 2
      tests/Avalonia.UnitTests/TestRoot.cs
  25. 70
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

5
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -253,8 +253,9 @@ namespace Direct3DInteropSample
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget,
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>());
return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget,
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>(),
AvaloniaLocator.Current.GetService<ImagingFactory>());
}
}

29
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -80,12 +80,29 @@ namespace Avalonia.Platform
/// <param name="cornerRadius">The corner radius.</param>
void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f);
/// <summary>
/// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer
/// for the current render target.
/// </summary>
/// <param name="size">The size of the layer in DIPs.</param>
/// <returns>An <see cref="IRenderTargetBitmapImpl"/></returns>
/// <remarks>
/// Depending on the rendering backend used, a layer created via this method may be more
/// performant than a standard render target bitmap. In particular the Direct2D backend
/// has to do a format conversion each time a standard render target bitmap is rendered,
/// but a layer created via this method has no such overhead.
/// </remarks>
IRenderTargetBitmapImpl CreateLayer(Size size);
/// <summary>
/// Pushes a clip rectange.
/// </summary>
/// <param name="clip">The clip rectangle.</param>
void PushClip(Rect clip);
/// <summary>
/// Pops the latest pushed clip rectangle.
/// </summary>
void PopClip();
/// <summary>
@ -94,10 +111,19 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity.</param>
void PushOpacity(double opacity);
/// <summary>
/// Pops the latest pushed opacity value.
/// </summary>
void PopOpacity();
/// <summary>
/// Pushes an opacity mask
/// </summary>
void PushOpacityMask(IBrush mask, Rect bounds);
/// <summary>
/// Pops the latest pushed opacity mask.
/// </summary>
void PopOpacityMask();
/// <summary>
@ -106,6 +132,9 @@ namespace Avalonia.Platform
/// <param name="clip">The clip geometry.</param>
void PushGeometryClip(IGeometryImpl clip);
/// <summary>
/// Pops the latest pushed geometry clip.
/// </summary>
void PopGeometryClip();
}
}

34
src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs

@ -1,34 +0,0 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class DefaultRenderLayerFactory : IRenderLayerFactory
{
private IPlatformRenderInterface _renderInterface;
public DefaultRenderLayerFactory()
: this(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>())
{
}
public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface)
{
_renderInterface = renderInterface;
}
public IRenderTargetBitmapImpl CreateLayer(
IVisual layerRoot,
Size size,
double dpiX,
double dpiY)
{
return _renderInterface.CreateRenderTargetBitmap(
(int)Math.Ceiling(size.Width),
(int)Math.Ceiling(size.Height),
dpiX,
dpiY);
}
}
}

159
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -26,7 +26,6 @@ namespace Avalonia.Rendering
private readonly IVisual _root;
private readonly ISceneBuilder _sceneBuilder;
private readonly RenderLayers _layers;
private readonly IRenderLayerFactory _layerFactory;
private bool _running;
private Scene _scene;
@ -45,13 +44,11 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param>
/// <param name="renderLoop">The render loop.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <param name="dispatcher">The dispatcher to use. Optional.</param>
public DeferredRenderer(
IRenderRoot root,
IRenderLoop renderLoop,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null,
IDispatcher dispatcher = null)
{
Contract.Requires<ArgumentNullException>(root != null);
@ -59,8 +56,7 @@ namespace Avalonia.Rendering
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
_layers = new RenderLayers();
_renderLoop = renderLoop;
}
@ -70,15 +66,13 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param>
/// <param name="renderTarget">The render target.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <remarks>
/// This constructor is intended to be used for unit testing.
/// </remarks>
public DeferredRenderer(
IVisual root,
IRenderTarget renderTarget,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null)
ISceneBuilder sceneBuilder = null)
{
Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(renderTarget != null);
@ -86,8 +80,7 @@ namespace Avalonia.Rendering
_root = root;
_renderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
_layers = new RenderLayers();
}
/// <inheritdoc/>
@ -180,38 +173,56 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false;
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
if (renderOverlay)
{
_dirtyRectsDisplay.Tick();
}
if (scene != null && scene.Size != Size.Empty)
try
{
if (scene.Generation != _lastSceneId)
using (var context = _renderTarget.CreateDrawingContext(this))
{
_layers.Update(scene);
RenderToLayers(scene);
if (DebugFramesPath != null)
if (scene != null && scene.Size != Size.Empty)
{
SaveDebugFrames(scene.Generation);
}
if (scene.Generation != _lastSceneId)
{
_layers.Update(scene, context);
_lastSceneId = scene.Generation;
RenderToLayers(scene);
composite = true;
}
if (DebugFramesPath != null)
{
SaveDebugFrames(scene.Generation);
}
if (renderOverlay)
{
RenderOverlay(scene);
RenderComposite(scene);
}
else if(composite)
{
RenderComposite(scene);
_lastSceneId = scene.Generation;
composite = true;
}
if (renderOverlay)
{
RenderOverlay(scene, context);
RenderComposite(scene, context);
}
else if (composite)
{
RenderComposite(scene, context);
}
}
}
}
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
}
}
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
@ -273,11 +284,11 @@ namespace Avalonia.Rendering
}
}
private void RenderOverlay(Scene scene)
private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent)
{
if (DrawDirtyRects)
{
var overlay = GetOverlay(scene.Size, scene.Scaling);
var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
using (var context = overlay.CreateDrawingContext(this))
{
@ -301,61 +312,44 @@ namespace Avalonia.Rendering
}
}
private void RenderComposite(Scene scene)
private void RenderComposite(Scene scene, IDrawingContextImpl context)
{
try
var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)
{
if (_renderTarget == null)
var bitmap = _layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
context.PushGeometryClip(layer.GeometryClip);
}
using (var context = _renderTarget.CreateDrawingContext(this))
if (layer.OpacityMask == null)
{
var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)
{
var bitmap = _layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
{
context.PushGeometryClip(layer.GeometryClip);
}
if (layer.OpacityMask == null)
{
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (layer.GeometryClip != null)
{
context.PopGeometryClip();
}
}
if (_overlay != null)
{
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
}
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (DrawFps)
{
RenderFps(context, clientRect, true);
}
if (layer.GeometryClip != null)
{
context.PopGeometryClip();
}
}
catch (RenderTargetCorruptedException ex)
if (_overlay != null)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
}
if (DrawFps)
{
RenderFps(context, clientRect, true);
}
}
@ -422,16 +416,19 @@ namespace Avalonia.Rendering
}
}
private IRenderTargetBitmapImpl GetOverlay(Size size, double scaling)
private IRenderTargetBitmapImpl GetOverlay(
IDrawingContextImpl parentContext,
Size size,
double scaling)
{
size = new Size(size.Width * scaling, size.Height * scaling);
var pixelSize = size * scaling;
if (_overlay == null ||
_overlay.PixelWidth != size.Width ||
_overlay.PixelHeight != size.Height)
_overlay.PixelWidth != pixelSize.Width ||
_overlay.PixelHeight != pixelSize.Height)
{
_overlay?.Dispose();
_overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling);
_overlay = parentContext.CreateLayer(size);
}
return _overlay;

11
src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs

@ -1,11 +0,0 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public interface IRenderLayerFactory
{
IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY);
}
}

10
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -7,16 +7,16 @@ namespace Avalonia.Rendering
{
public class RenderLayer
{
private readonly IRenderLayerFactory _factory;
private readonly IDrawingContextImpl _drawingContext;
public RenderLayer(
IRenderLayerFactory factory,
IDrawingContextImpl drawingContext,
Size size,
double scaling,
IVisual layerRoot)
{
_factory = factory;
Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling);
_drawingContext = drawingContext;
Bitmap = drawingContext.CreateLayer(size);
Size = size;
Scaling = scaling;
LayerRoot = layerRoot;
@ -31,7 +31,7 @@ namespace Avalonia.Rendering
{
if (Size != size || Scaling != scaling)
{
var resized = _factory.CreateLayer(LayerRoot, size * scaling, 96 * scaling, 96 * scaling);
var resized = _drawingContext.CreateLayer(size);
using (var context = resized.CreateDrawingContext(null))
{

9
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree;
@ -8,19 +9,17 @@ namespace Avalonia.Rendering
{
public class RenderLayers : IEnumerable<RenderLayer>
{
private readonly IRenderLayerFactory _factory;
private List<RenderLayer> _inner = new List<RenderLayer>();
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
public RenderLayers(IRenderLayerFactory factory)
public RenderLayers()
{
_factory = factory;
}
public int Count => _inner.Count;
public RenderLayer this[IVisual layerRoot] => _index[layerRoot];
public void Update(Scene scene)
public void Update(Scene scene, IDrawingContextImpl context)
{
for (var i = scene.Layers.Count - 1; i >= 0; --i)
{
@ -29,7 +28,7 @@ namespace Avalonia.Rendering
if (!_index.TryGetValue(src.LayerRoot, out layer))
{
layer = new RenderLayer(_factory, scene.Size, scene.Scaling, src.LayerRoot);
layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot);
_inner.Add(layer);
_index.Add(src.LayerRoot, layer);
}

5
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -194,6 +194,11 @@ namespace Avalonia.Rendering.SceneGraph
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
}
/// <inheritdoc/>
public void PopClip()
{

6
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -354,6 +354,12 @@ namespace Avalonia.Skia
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var pixelSize = size * (_dpi / 96);
return new BitmapImpl((int)pixelSize.Width, (int)pixelSize.Height, _dpi);
}
public void PushClip(Rect clip)
{
Canvas.Save();

4
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -145,7 +145,7 @@ namespace Avalonia.Direct2D1
return new HwndRenderTarget(nativeWindow);
}
if (s is IExternalDirect2DRenderTargetSurface external)
return new ExternalRenderTarget(external, s_dwfactory);
return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory);
if (s is IFramebufferPlatformSurface fb)
return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory);
}
@ -158,7 +158,7 @@ namespace Avalonia.Direct2D1
double dpiX,
double dpiY)
{
return new RenderTargetBitmapImpl(
return new WicRenderTargetBitmapImpl(
s_imagingFactory,
s_d2D1Factory,
s_dwfactory,

28
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
@ -11,15 +8,20 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1
{
class ExternalRenderTarget : IRenderTarget
class ExternalRenderTarget : IRenderTarget, ILayerFactory
{
private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
private readonly DirectWriteFactory _dwFactory;
public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory)
private readonly SharpDX.WIC.ImagingFactory _wicFactory;
public ExternalRenderTarget(
IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory,
SharpDX.WIC.ImagingFactory wicFactory)
{
_externalRenderTargetProvider = externalRenderTargetProvider;
_dwFactory = dwFactory;
_wicFactory = wicFactory;
}
public void Dispose()
@ -31,7 +33,7 @@ namespace Avalonia.Direct2D1
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
_externalRenderTargetProvider.BeforeDrawing();
return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () =>
return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () =>
{
try
{
@ -43,5 +45,15 @@ namespace Avalonia.Direct2D1
}
});
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
return D2DRenderTargetBitmapImpl.CreateCompatible(
_wicFactory,
_dwFactory,
target,
size);
}
}
}

2
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@ -47,7 +47,7 @@ namespace Avalonia.Direct2D1
.CreateDrawingContext(visualBrushRenderer);
}
class FramebufferShim : RenderTargetBitmapImpl
class FramebufferShim : WicRenderTargetBitmapImpl
{
private readonly ILockedFramebuffer _target;

10
src/Windows/Avalonia.Direct2D1/ILayerFactory.cs

@ -0,0 +1,10 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Direct2D1
{
public interface ILayerFactory
{
IRenderTargetBitmapImpl CreateLayer(Size size);
}
}

39
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -2,16 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
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;
using IBitmap = Avalonia.Media.Imaging.IBitmap;
namespace Avalonia.Direct2D1.Media
{
@ -21,9 +18,11 @@ namespace Avalonia.Direct2D1.Media
public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{
private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly ILayerFactory _layerFactory;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback;
private readonly SharpDX.WIC.ImagingFactory _imagingFactory;
private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary>
@ -31,21 +30,30 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
/// <param name="visualBrushRenderer">The visual brush renderer.</param>
/// <param name="renderTarget">The render target to draw to.</param>
/// <param name="layerFactory">
/// An object to use to create layers. May be null, in which case a
/// <see cref="WicRenderTargetBitmapImpl"/> will created when a new layer is requested.
/// </param>
/// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="imagingFactory">The WIC imaging factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
/// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param>
public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer,
ILayerFactory layerFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory,
SharpDX.WIC.ImagingFactory imagingFactory,
SharpDX.DXGI.SwapChain1 swapChain = null,
Action finishedCallback = null)
{
_visualBrushRenderer = visualBrushRenderer;
_layerFactory = layerFactory;
_renderTarget = renderTarget;
_swapChain = swapChain;
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
_swapChain = swapChain;
_renderTarget.BeginDraw();
}
@ -97,7 +105,7 @@ namespace Avalonia.Direct2D1.Media
using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
{
_renderTarget.DrawBitmap(
d2d,
d2d.Value,
destRect.ToSharpDX(),
(float)opacity,
BitmapInterpolationMode.Linear,
@ -115,7 +123,7 @@ namespace Avalonia.Direct2D1.Media
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 sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D()))
{
@ -284,6 +292,25 @@ namespace Avalonia.Direct2D1.Media
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_layerFactory != null)
{
return _layerFactory.CreateLayer(size);
}
else
{
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height);
var pixelSize = size * (dpi / 96);
return platform.CreateRenderTargetBitmap(
(int)pixelSize.Width,
(int)pixelSize.Height,
dpi.X,
dpi.Y);
}
}
/// <summary>
/// Pushes a clip rectange.
/// </summary>
@ -397,7 +424,7 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl(
visualBrush,
_renderTarget,
new D2DBitmapImpl(intermediate.Bitmap),
new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap),
destinationSize);
}
}

7
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -10,6 +10,8 @@ namespace Avalonia.Direct2D1.Media
{
public sealed class ImageBrushImpl : BrushImpl
{
OptionalDispose<Bitmap> _bitmap;
public ImageBrushImpl(
ITileBrush brush,
SharpDX.Direct2D1.RenderTarget target,
@ -20,9 +22,10 @@ namespace Avalonia.Direct2D1.Media
if (!calc.NeedsIntermediate)
{
_bitmap = bitmap.GetDirect2DBitmap(target);
PlatformBrush = new BitmapBrush(
target,
bitmap.GetDirect2DBitmap(target),
_bitmap.Value,
GetBitmapBrushProperties(brush),
GetBrushProperties(brush, calc.DestinationRect));
}
@ -41,7 +44,7 @@ namespace Avalonia.Direct2D1.Media
public override void Dispose()
{
((BitmapBrush)PlatformBrush)?.Bitmap.Dispose();
_bitmap.Dispose();
base.Dispose();
}

32
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -1,20 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
public abstract class BitmapImpl : IBitmapImpl, IDisposable
{
public abstract Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
public BitmapImpl(ImagingFactory imagingFactory)
{
WicImagingFactory = imagingFactory;
}
public ImagingFactory WicImagingFactory { get; }
public abstract int PixelWidth { get; }
public abstract int PixelHeight { get; }
public abstract void Save(string fileName);
public abstract OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
public void Save(string fileName)
{
if (Path.GetExtension(fileName) != ".png")
{
// Yeah, we need to support other formats.
throw new NotSupportedException("Use PNG, stoopid.");
}
using (FileStream s = new FileStream(fileName, FileMode.Create))
{
Save(s);
}
}
public abstract void Save(Stream stream);
public virtual void Dispose()

47
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using SharpDX.Direct2D1;
using WICFactory = SharpDX.WIC.ImagingFactory;
using ImagingFactory2 = SharpDX.WIC.ImagingFactory2;
using ImageParameters = SharpDX.WIC.ImageParameters;
using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder;
namespace Avalonia.Direct2D1.Media
{
@ -26,32 +25,42 @@ namespace Avalonia.Direct2D1.Media
/// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable.
/// </remarks>
public D2DBitmapImpl(Bitmap d2DBitmap)
public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap)
: base(imagingFactory)
{
if (d2DBitmap == null) throw new ArgumentNullException(nameof(d2DBitmap));
_direct2D = d2DBitmap;
_direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
}
public override Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) => _direct2D;
public override int PixelWidth => _direct2D.PixelSize.Width;
public override int PixelHeight => _direct2D.PixelSize.Height;
public override void Save(string fileName)
public override void Dispose()
{
throw new NotImplementedException();
base.Dispose();
_direct2D.Dispose();
}
public override void Save(Stream stream)
public override OptionalDispose<Bitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
throw new NotImplementedException();
return new OptionalDispose<Bitmap>(_direct2D, false);
}
public override void Dispose()
public override void Save(Stream stream)
{
base.Dispose();
_direct2D.Dispose();
using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder))
using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null))
{
var parameters = new ImageParameters(
new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied),
_direct2D.DotsPerInch.Width,
_direct2D.DotsPerInch.Height,
0, 0, PixelWidth, PixelHeight);
imageEncoder.WriteFrame(_direct2D, frameEncode, parameters);
frameEncode.Commit();
encoder.Commit();
}
}
}
}

68
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -0,0 +1,68 @@
using System;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media.Imaging
{
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly BitmapRenderTarget _target;
public D2DRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
BitmapRenderTarget target)
: base(imagingFactory, target.Bitmap)
{
_dwriteFactory = dwriteFactory;
_target = target;
}
public override int PixelWidth => _target.PixelSize.Width;
public override int PixelHeight => _target.PixelSize.Height;
public static D2DRenderTargetBitmapImpl CreateCompatible(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
Size size)
{
var bitmapRenderTarget = new BitmapRenderTarget(
renderTarget,
CompatibleRenderTargetOptions.None,
new Size2F((float)size.Width, (float)size.Height));
return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget);
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(
visualBrushRenderer,
this,
_target,
_dwriteFactory,
WicImagingFactory);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size);
}
public override void Dispose()
{
_target.Dispose();
}
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
return new OptionalDispose<D2DBitmap>(_target.Bitmap, false);
}
}
}

58
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -3,11 +3,10 @@
using System;
using System.IO;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using PixelFormat = SharpDX.WIC.PixelFormat;
using APixelFormat = Avalonia.Platform.PixelFormat;
using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
@ -16,18 +15,14 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public class WicBitmapImpl : BitmapImpl
{
private readonly ImagingFactory _factory;
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="fileName">The filename of the bitmap to load.</param>
public WicBitmapImpl(ImagingFactory factory, string fileName)
: base(factory)
{
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand))
{
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
@ -40,9 +35,8 @@ namespace Avalonia.Direct2D1.Media
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="stream">The stream to read the bitmap from.</param>
public WicBitmapImpl(ImagingFactory factory, Stream stream)
: base(factory)
{
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad))
{
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
@ -57,11 +51,11 @@ namespace Avalonia.Direct2D1.Media
/// <param name="height">The height of the bitmap.</param>
/// <param name="pixelFormat">Pixel format</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null)
: base(factory)
{
if (!pixelFormat.HasValue)
pixelFormat = APixelFormat.Bgra8888;
_factory = factory;
PixelFormat = pixelFormat;
WicImpl = new Bitmap(
factory,
@ -71,10 +65,10 @@ namespace Avalonia.Direct2D1.Media
BitmapCreateCacheOption.CacheOnLoad);
}
public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride)
public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride)
: base(factory)
{
WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
_factory = factory;
PixelFormat = format;
using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{
@ -113,41 +107,23 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
/// <param name="renderTarget">The render target.</param>
/// <returns>The Direct2D bitmap.</returns>
public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{
FormatConverter converter = new FormatConverter(_factory);
FormatConverter converter = new FormatConverter(WicImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
}
/// <summary>
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
public override void Save(string fileName)
public override void Save(Stream stream)
{
if (Path.GetExtension(fileName) != ".png")
{
// Yeah, we need to support other formats.
throw new NotSupportedException("Use PNG, stoopid.");
}
using (FileStream s = new FileStream(fileName, FileMode.Create))
using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))
{
Save(s);
frame.Initialize();
frame.WriteSource(WicImpl);
frame.Commit();
encoder.Commit();
}
}
public override void Save(Stream stream)
{
PngBitmapEncoder encoder = new PngBitmapEncoder(_factory);
encoder.Initialize(stream);
BitmapFrameEncode frame = new BitmapFrameEncode(encoder);
frame.Initialize();
frame.WriteSource(WicImpl);
frame.Commit();
encoder.Commit();
}
}
}

6
src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs → src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -10,12 +10,12 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media
{
public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target;
public RenderTargetBitmapImpl(
public WicRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
Factory d2dFactory,
DirectWriteFactory dwriteFactory,
@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.Media
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback)
{
return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory,
return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory,
finishedCallback: finishedCallback);
}
}

22
src/Windows/Avalonia.Direct2D1/OptionalDispose.cs

@ -0,0 +1,22 @@
using System;
namespace Avalonia.Direct2D1
{
public struct OptionalDispose<T> : IDisposable where T : IDisposable
{
private readonly bool _dispose;
public OptionalDispose(T value, bool dispose)
{
Value = value;
_dispose = dispose;
}
public T Value { get; }
public void Dispose()
{
if (_dispose) Value?.Dispose();
}
}
}

34
src/Windows/Avalonia.Direct2D1/RenderTarget.cs

@ -3,14 +3,16 @@
using System;
using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX.Direct2D1;
using DwFactory = SharpDX.DirectWrite.Factory;
using WicFactory = SharpDX.WIC.ImagingFactory;
namespace Avalonia.Direct2D1
{
public class RenderTarget : IRenderTarget
public class RenderTarget : IRenderTarget, ILayerFactory
{
/// <summary>
/// The render target.
@ -25,24 +27,13 @@ namespace Avalonia.Direct2D1
{
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<DwFactory>();
WicFactory = AvaloniaLocator.Current.GetService<WicFactory>();
_renderTarget = renderTarget;
}
/// <summary>
/// Gets the Direct2D factory.
/// </summary>
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public DwFactory DirectWriteFactory
{
get;
}
public Factory Direct2DFactory { get; }
public DwFactory DirectWriteFactory { get; }
public WicFactory WicFactory { get; }
/// <summary>
/// Creates a drawing context for a rendering session.
@ -50,7 +41,16 @@ namespace Avalonia.Direct2D1
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory);
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicFactory,
DirectWriteFactory,
_renderTarget,
size);
}
public void Dispose()

40
src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs

@ -10,10 +10,11 @@ using Factory = SharpDX.Direct2D1.Factory;
using Factory2 = SharpDX.DXGI.Factory2;
using Avalonia.Rendering;
using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
namespace Avalonia.Direct2D1
{
public abstract class SwapChainRenderTarget : IRenderTarget
public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory
{
private Size2 _savedSize;
private Size2F _savedDpi;
@ -26,24 +27,12 @@ namespace Avalonia.Direct2D1
D2DDevice = AvaloniaLocator.Current.GetService<Device>();
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
WicImagingFactory = AvaloniaLocator.Current.GetService<SharpDX.WIC.ImagingFactory>();
}
/// <summary>
/// Gets the Direct2D factory.
/// </summary>
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public SharpDX.DirectWrite.Factory DirectWriteFactory
{
get;
}
public Factory Direct2DFactory { get; }
public SharpDX.DirectWrite.Factory DirectWriteFactory { get; }
public SharpDX.WIC.ImagingFactory WicImagingFactory { get; }
protected SharpDX.DXGI.Device DxgiDevice { get; }
@ -67,11 +56,27 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(
visualBrushRenderer,
this,
_deviceContext,
DirectWriteFactory,
WicImagingFactory,
_swapChain);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_deviceContext == null)
{
CreateSwapChain();
}
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicImagingFactory,
DirectWriteFactory,
_deviceContext,
size);
}
public void Dispose()
{
_deviceContext?.Dispose();
@ -86,7 +91,6 @@ namespace Avalonia.Direct2D1
_deviceContext?.Dispose();
_deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi};
var swapChainDesc = new SwapChainDescription1
{
Width = _savedSize.Width,

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -49,8 +49,6 @@ namespace Avalonia.UnitTests
public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService<ILayoutManager>();
public IRenderTarget RenderTarget => null;
public IRenderer Renderer { get; set; }
public IAccessKeyHandler AccessKeyHandler => null;

70
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -30,7 +30,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: MockSceneBuilder(root).Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher.Object);
target.Start();
@ -55,7 +54,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -75,7 +73,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -111,7 +108,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -146,22 +142,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering
scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize));
});
var layers = new Mock<IRenderLayerFactory>();
layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer());
var renderInterface = new Mock<IPlatformRenderInterface>();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: layers.Object,
//layerFactory: layers.Object,
dispatcher: dispatcher);
target.Start();
RunFrame(loop);
layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96));
var context = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
context.Verify(x => x.CreateLayer(root.ClientSize));
}
[Fact]
@ -185,25 +179,25 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>();
var layerFactory = new MockRenderLayerFactory(new Dictionary<IVisual, IRenderTargetBitmapImpl>
{
{ root, CreateLayer() },
{ border, CreateLayer() },
});
var rootLayer = CreateLayer();
var borderLayer = CreateLayer();
var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny<Size>()))
.Returns(rootLayer)
.Returns(borderLayer);
var loop = new Mock<IRenderLoop>();
var target = new DeferredRenderer(
root,
root,
loop.Object,
layerFactory: layerFactory,
dispatcher: new ImmediateDispatcher());
root.Renderer = target;
target.Start();
RunFrame(loop);
var rootContext = layerFactory.GetMockDrawingContext(root);
var borderContext = layerFactory.GetMockDrawingContext(border);
var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null));
var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null));
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
@ -223,7 +217,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
border.Opacity = 1;
RunFrame(loop);
layerFactory.GetMockBitmap(border).Verify(x => x.Dispose());
Mock.Get(borderLayer).Verify(x => x.Dispose());
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never);
@ -246,13 +240,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>()) == Mock.Of<IDrawingContextImpl>());
}
private Mock<IRenderLayerFactory> MockLayerFactory(IRenderRoot root)
{
var result = new Mock<IRenderLayerFactory>();
result.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer());
return result;
}
private Mock<ISceneBuilder> MockSceneBuilder(IRenderRoot root)
{
var result = new Mock<ISceneBuilder>();
@ -260,34 +247,5 @@ namespace Avalonia.Visuals.UnitTests.Rendering
.Callback<Scene>(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)));
return result;
}
private class MockRenderLayerFactory : IRenderLayerFactory
{
private IDictionary<IVisual, IRenderTargetBitmapImpl> _layers;
public MockRenderLayerFactory(IDictionary<IVisual, IRenderTargetBitmapImpl> layers)
{
_layers = layers;
}
public IRenderTargetBitmapImpl CreateLayer(
IVisual layerRoot,
Size size,
double dpiX,
double dpiY)
{
return _layers[layerRoot];
}
public Mock<IRenderTargetBitmapImpl> GetMockBitmap(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot]);
}
public Mock<IDrawingContextImpl> GetMockDrawingContext(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot].CreateDrawingContext(null));
}
}
}
}

Loading…
Cancel
Save