Browse Source

Reworked the way we are clipping dirty rects with CompositionTarget (#14806)

* Reworked dirty rect tracking to support regions

* Fixed FPS counter
pull/16303/head
Nikita Tsukanov 2 years ago
committed by GitHub
parent
commit
4dbb165a7b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      samples/ControlCatalog.NetCore/Program.cs
  2. 4
      samples/ControlCatalog/Pages/CompositionPage.axaml
  3. 64
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  4. 2
      src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs
  5. 11
      src/Avalonia.Base/PixelSize.cs
  6. 33
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  7. 3
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  8. 17
      src/Avalonia.Base/Platform/IPlatformRenderInterfaceRegion.cs
  9. 3
      src/Avalonia.Base/Platform/IRenderTarget.cs
  10. 3
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  11. 32
      src/Avalonia.Base/Rendering/Composition/CompositionCustomVisual.cs
  12. 47
      src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
  13. 16
      src/Avalonia.Base/Rendering/Composition/CompositionOptions.cs
  14. 1
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  15. 2
      src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs
  16. 103
      src/Avalonia.Base/Rendering/Composition/Server/DirtyRectTracker.cs
  17. 11
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  18. 13
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
  19. 6
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  20. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionExperimentalAcrylicVisual.cs
  21. 3
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs
  22. 3
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs
  23. 35
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.DirtyRects.cs
  24. 121
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  25. 21
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  26. 1
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  27. 19
      src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs
  28. 2
      src/Avalonia.Base/composition-schema.xml
  29. 5
      src/Avalonia.Controls/BorderVisual.cs
  30. 2
      src/Avalonia.X11/X11CursorFactory.cs
  31. 2
      src/Avalonia.X11/X11IconLoader.cs
  32. 26
      src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  33. 95
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  34. 5
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  35. 3
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  36. 1
      src/Skia/Avalonia.Skia/PictureRenderTarget.cs
  37. 3
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  38. 3
      src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs
  39. 53
      src/Skia/Avalonia.Skia/SkiaRegionImpl.cs
  40. 10
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  41. 3
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  42. 3
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  43. 4
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  44. 8
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  45. 58
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  46. 2
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  47. 7
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  48. 8
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  49. 4
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  50. 4
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  51. 2
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  52. 6
      tests/Avalonia.UnitTests/TestRoot.cs

5
samples/ControlCatalog.NetCore/Program.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Fonts.Inter;
using Avalonia.Headless;
using Avalonia.LogicalTree;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using ControlCatalog.Pages;
@ -130,6 +131,10 @@ namespace ControlCatalog.NetCore
UseDBusMenu = true,
EnableIme = true
})
.With(new CompositionOptions()
{
UseRegionDirtyRectClipping = true
})
.UseSkia()
.WithInterFont()
.AfterSetup(builder =>

4
samples/ControlCatalog/Pages/CompositionPage.axaml

@ -50,6 +50,10 @@
<Button Margin="10" Click="ButtonThreadSleep">Thread.Sleep(10000);</Button>
<Button Margin="10" Click="ButtonStartCustomVisual">Start</Button>
<Button Margin="10" Click="ButtonStopCustomVisual">Stop</Button>
<CheckBox Margin="10"
x:Name="PreciseDirtyRectsCheckboxCustomVisual"
IsCheckedChanged="PreciseDirtyRectsCheckboxCustomVisualChanged"
>Precise dirty rects</CheckBox>
</StackPanel>
<Control x:Name="CustomVisualHost" />
</DockPanel>

64
samples/ControlCatalog/Pages/CompositionPage.axaml.cs

@ -24,7 +24,7 @@ public partial class CompositionPage : UserControl
public CompositionPage()
{
AvaloniaXamlLoader.Load(this);
InitializeComponent();
AttachAnimatedSolidVisual(this.FindControl<Control>("SolidVisualHost")!);
AttachCustomVisual(this.FindControl<Control>("CustomVisualHost")!);
}
@ -206,6 +206,7 @@ public partial class CompositionPage : UserControl
_customVisual = compositor.CreateCustomVisual(new CustomVisualHandler());
ElementComposition.SetElementChildVisual(v, _customVisual);
_customVisual.SendHandlerMessage(CustomVisualHandler.StartMessage);
PreciseDirtyRectsCheckboxCustomVisualChanged(this, new());
Update();
};
@ -221,10 +222,16 @@ public partial class CompositionPage : UserControl
private TimeSpan _animationElapsed;
private TimeSpan? _lastServerTime;
private bool _running;
private bool _preciseDirtyRects;
public static readonly object StopMessage = new(), StartMessage = new();
public override void OnRender(ImmediateDrawingContext drawingContext)
public static readonly object StopMessage = new(),
StartMessage = new(),
UsePreciseDirtyRects = new(),
UseNonPreciseDirtyRects = new();
private List<(Point center, double size, ImmutableSolidColorBrush brush)> _ellipses = new();
void UpdateRects()
{
if (_running)
{
@ -232,6 +239,8 @@ public partial class CompositionPage : UserControl
_lastServerTime = CompositionNow;
}
_ellipses.Clear();
const int cnt = 20;
var maxPointSizeX = EffectiveSize.X / (cnt * 1.6);
var maxPointSizeY = EffectiveSize.Y / 4;
@ -250,16 +259,22 @@ public partial class CompositionPage : UserControl
var posY = (EffectiveSize.Y - pointSize) * (1 + Math.Sin(stage * 3.14 * 3 + sinOffset)) / 2 + pointSize / 2;
var opacity = Math.Sin(stage * 3.14);
drawingContext.DrawEllipse(new ImmutableSolidColorBrush(Color.FromArgb(
255,
(byte)(255 - 255 * colorStage),
(byte)(255 * Math.Abs(0.5 - colorStage) * 2),
(byte)(255 * colorStage)
), opacity), null,
new Point(posX, posY), pointSize / 2, pointSize / 2);
_ellipses.Add((new Point(posX, posY), pointSize / 2, new ImmutableSolidColorBrush(Color.FromArgb(
255,
(byte)(255 - 255 * colorStage),
(byte)(255 * Math.Abs(0.5 - colorStage) * 2),
(byte)(255 * colorStage)
), opacity)));
}
}
public override void OnRender(ImmediateDrawingContext drawingContext)
{
if (_ellipses.Count == 0)
UpdateRects();
foreach(var e in _ellipses)
drawingContext.DrawEllipse(e.brush, null, e.center, e.size, e.size);
}
public override void OnMessage(object message)
@ -272,13 +287,29 @@ public partial class CompositionPage : UserControl
}
else if (message == StopMessage)
_running = false;
else if (message == UsePreciseDirtyRects)
_preciseDirtyRects = true;
else if (message == UseNonPreciseDirtyRects)
_preciseDirtyRects = false;
}
void InvalidateCurrentEllipseRects()
{
foreach (var e in _ellipses)
Invalidate(new Rect(e.center.X - e.size, e.center.Y - e.size, e.size * 2, e.size * 2));
}
public override void OnAnimationFrameUpdate()
{
if (_running)
{
Invalidate();
if (_preciseDirtyRects)
InvalidateCurrentEllipseRects();
else
Invalidate();
UpdateRects();
if(_preciseDirtyRects)
InvalidateCurrentEllipseRects();
RegisterForNextAnimationFrameUpdate();
}
}
@ -298,6 +329,13 @@ public partial class CompositionPage : UserControl
{
_customVisual?.SendHandlerMessage(CustomVisualHandler.StopMessage);
}
private void PreciseDirtyRectsCheckboxCustomVisualChanged(object sender, RoutedEventArgs e)
{
_customVisual?.SendHandlerMessage(PreciseDirtyRectsCheckboxCustomVisual?.IsChecked == true
? CustomVisualHandler.UsePreciseDirtyRects
: CustomVisualHandler.UseNonPreciseDirtyRects);
}
}
public class CompositionPageColorItem

2
src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs

@ -77,7 +77,7 @@ namespace Avalonia.Media.Imaging
/// <returns>The drawing context.</returns>
public DrawingContext CreateDrawingContext(bool clear)
{
var platform = PlatformImpl.Item.CreateDrawingContext();
var platform = PlatformImpl.Item.CreateDrawingContext(true);
if(clear)
platform.Clear(Colors.Transparent);
return new PlatformDrawingContext(platform);

11
src/Avalonia.Base/PixelSize.cs

@ -166,6 +166,17 @@ namespace Avalonia
public static PixelSize FromSize(Size size, double scale) => new PixelSize(
(int)Math.Ceiling(size.Width * scale),
(int)Math.Ceiling(size.Height * scale));
/// <summary>
/// A reversible variant of <see cref="FromSize(Size, double)"/> that uses Round instead of Ceiling to make it reversible from ToSize
/// </summary>
/// <param name="size">The size.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
internal static PixelSize FromSizeRounded(Size size, double scale) => new PixelSize(
(int)Math.Round(size.Width * scale),
(int)Math.Round(size.Height * scale));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified scaling factor.

33
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -3,6 +3,7 @@ using Avalonia.Media;
using Avalonia.Utilities;
using Avalonia.Metadata;
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
namespace Avalonia.Platform
{
@ -75,6 +76,18 @@ namespace Avalonia.Platform
/// </remarks>
void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect,
BoxShadows boxShadows = default);
/// <summary>
/// Draws the specified region with the specified Brush and Pen.
/// </summary>
/// <param name="brush">The brush used to fill the rectangle, or <c>null</c> for no fill.</param>
/// <param name="pen">The pen used to stroke the rectangle, or <c>null</c> for no stroke.</param>
/// <param name="region">The region to draw.</param>
/// <remarks>
/// The brush and the pen can both be null. If the brush is null, then no fill is performed.
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
/// </remarks>
void DrawRegion(IBrush? brush, IPen? pen, IPlatformRenderInterfaceRegion region);
/// <summary>
/// Draws an ellipse with the specified Brush and Pen.
@ -108,7 +121,7 @@ namespace Avalonia.Platform
/// 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>
IDrawingContextLayerImpl CreateLayer(Size size);
IDrawingContextLayerImpl CreateLayer(PixelSize size);
/// <summary>
/// Pushes a clip rectangle.
@ -121,12 +134,28 @@ namespace Avalonia.Platform
/// </summary>
/// <param name="clip">The clip rounded rectangle</param>
void PushClip(RoundedRect clip);
/// <summary>
/// Pushes a clip region.
/// </summary>
/// <param name="region">The clip region</param>
void PushClip(IPlatformRenderInterfaceRegion region);
/// <summary>
/// Pops the latest pushed clip rectangle.
/// </summary>
void PopClip();
/// <summary>
/// Enforces rendering to happen on an intermediate surface
/// </summary>
void PushLayer(Rect bounds);
/// <summary>
/// Pops the latest pushed intermediate surface layer.
/// </summary>
void PopLayer();
/// <summary>
/// Pushes an opacity value.
/// </summary>

3
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -199,6 +199,9 @@ namespace Avalonia.Platform
public PixelFormat DefaultPixelFormat { get; }
bool IsSupportedBitmapPixelFormat(PixelFormat format);
bool SupportsRegions { get; }
IPlatformRenderInterfaceRegion CreateRegion();
}
[Unstable, PrivateApi]

17
src/Avalonia.Base/Platform/IPlatformRenderInterfaceRegion.cs

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using Avalonia.Metadata;
namespace Avalonia.Platform;
[Unstable, PrivateApi]
public interface IPlatformRenderInterfaceRegion : IDisposable
{
void AddRect(PixelRect rect);
void Reset();
bool IsEmpty { get; }
PixelRect Bounds { get; }
IList<PixelRect> Rects { get; }
bool Intersects(Rect rect);
bool Contains(Point pt);
}

3
src/Avalonia.Base/Platform/IRenderTarget.cs

@ -16,7 +16,8 @@ namespace Avalonia.Platform
/// <summary>
/// Creates an <see cref="IDrawingContextImpl"/> for a rendering session.
/// </summary>
IDrawingContextImpl CreateDrawingContext();
/// <param name="useScaledDrawing">Apply DPI reported by the render target as a hidden transform matrix</param>
IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing);
/// <summary>
/// Indicates if the render target is no longer usable and needs to be recreated

3
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -172,7 +172,8 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
v.SynchronizeCompositionChildVisuals();
_dirty.Clear();
_recalculateChildren.Clear();
CompositionTarget.Size = _root.ClientSize;
CompositionTarget.PixelSize = PixelSize.FromSizeRounded(_root.ClientSize, _root.RenderScaling);
CompositionTarget.Scaling = _root.RenderScaling;
var commit = _compositor.RequestCommitAsync();

32
src/Avalonia.Base/Rendering/Composition/CompositionCustomVisual.cs

@ -1,12 +1,15 @@
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Threading;
namespace Avalonia.Rendering.Composition;
public sealed class CompositionCustomVisual : CompositionContainerVisual
{
private static readonly ThreadSafeObjectPool<List<object>> s_messageListPool = new();
private List<object>? _messages;
internal CompositionCustomVisual(Compositor compositor, CompositionCustomVisualHandler handler)
@ -17,21 +20,26 @@ public sealed class CompositionCustomVisual : CompositionContainerVisual
public void SendHandlerMessage(object message)
{
(_messages ??= new()).Add(message);
RegisterForSerialization();
if (_messages == null)
{
_messages = s_messageListPool.Get();
Compositor.RequestCompositionUpdate(OnCompositionUpdate);
}
_messages.Add(message);
}
private protected override void SerializeChangesCore(BatchStreamWriter writer)
private void OnCompositionUpdate()
{
base.SerializeChangesCore(writer);
if (_messages == null || _messages.Count == 0)
writer.Write(0);
else
if(_messages == null)
return;
var messages = _messages;
_messages = null;
Compositor.PostServerJob(()=>
{
writer.Write(_messages.Count);
foreach (var m in _messages)
writer.WriteObject(m);
_messages.Clear();
}
((ServerCompositionCustomVisual)Server).DispatchMessages(messages);
messages.Clear();
s_messageListPool.ReturnAndSetNull(ref messages);
});
}
}

47
src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Collections.Pooled;
using Avalonia.Media;
using Avalonia.Rendering.Composition.Server;
@ -8,6 +10,8 @@ namespace Avalonia.Rendering.Composition;
public abstract class CompositionCustomVisualHandler
{
private ServerCompositionCustomVisual? _host;
private bool _inRender;
private Rect _currentTransformedClip;
public virtual void OnMessage(object message)
{
@ -18,7 +22,21 @@ public abstract class CompositionCustomVisualHandler
{
}
internal void Render(ImmediateDrawingContext drawingContext, Rect currentTransformedClip)
{
_inRender = true;
_currentTransformedClip = currentTransformedClip;
try
{
OnRender(drawingContext);
}
finally
{
_inRender = false;
}
}
public abstract void OnRender(ImmediateDrawingContext drawingContext);
void VerifyAccess()
@ -28,6 +46,13 @@ public abstract class CompositionCustomVisualHandler
_host.Compositor.VerifyAccess();
}
void VerifyInRender()
{
VerifyAccess();
if (!_inRender)
throw new InvalidOperationException("This API is only available from OnRender");
}
protected Vector EffectiveSize
{
get
@ -57,9 +82,29 @@ public abstract class CompositionCustomVisualHandler
_host!.HandlerInvalidate();
}
protected void Invalidate(Rect rc)
{
VerifyAccess();
_host!.HandlerInvalidate(rc);
}
protected void RegisterForNextAnimationFrameUpdate()
{
VerifyAccess();
_host!.HandlerRegisterForNextAnimationFrameUpdate();
}
protected bool RenderClipContains(Point pt)
{
VerifyInRender();
pt *= _host!.GlobalTransformMatrix;
return _currentTransformedClip.Contains(pt) && _host.Root!.DirtyRects.Contains(pt);
}
protected bool RenderClipIntersectes(Rect rc)
{
VerifyInRender();
rc = rc.TransformToAABB(_host!.GlobalTransformMatrix);
return _currentTransformedClip.Intersects(rc) && _host.Root!.DirtyRects.Intersects(rc);
}
}

16
src/Avalonia.Base/Rendering/Composition/CompositionOptions.cs

@ -0,0 +1,16 @@
namespace Avalonia.Rendering.Composition;
public class CompositionOptions
{
/// <summary>
/// Enables more accurate tracking of dirty rects by utilizing regions if supported by the underlying
/// drawing context
/// </summary>
public bool? UseRegionDirtyRectClipping { get; set; }
/// <summary>
/// Enforces dirty contents to be rendered into an extra intermediate surface before being applied onto the
/// saved frame.
/// Required as a workaround for Skia bug https://issues.skia.org/issues/327877721
/// </summary>
public bool? UseSaveLayerRootClip { get; set; }
}

1
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -29,6 +29,7 @@ namespace Avalonia.Rendering.Composition
/// <returns></returns>
public PooledList<CompositionVisual>? TryHitTest(Point point, CompositionVisual? root, Func<CompositionVisual, bool>? filter)
{
point *= Scaling;
Server.Readback.NextRead();
root ??= Root;
if (root == null)

2
src/Avalonia.Base/Rendering/Composition/Server/DiagnosticTextRenderer.cs

@ -67,7 +67,7 @@ namespace Avalonia.Rendering.Composition.Server
{
var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
var run = _runs[effectiveChar - FirstChar];
context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0);
context.Transform = Matrix.CreateTranslation(offset, 0.0) * originalTransform;
context.DrawGlyphRun(foreground, run.PlatformImpl.Item);
offset += run.Bounds.Width;
}

103
src/Avalonia.Base/Rendering/Composition/Server/DirtyRectTracker.cs

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.Reactive;
namespace Avalonia.Rendering.Composition.Server;
internal interface IDirtyRectTracker
{
void AddRect(PixelRect rect);
IDisposable BeginDraw(IDrawingContextImpl ctx);
bool IsEmpty { get; }
bool Intersects(Rect rect);
bool Contains(Point pt);
void Reset();
void Visualize(IDrawingContextImpl context);
PixelRect CombinedRect { get; }
IList<PixelRect> Rects { get; }
}
internal class DirtyRectTracker : IDirtyRectTracker
{
private PixelRect _rect;
private Rect _doubleRect;
private PixelRect[] _rectsForApi = new PixelRect[1];
private Random _random = new();
public void AddRect(PixelRect rect)
{
_rect = _rect.Union(rect);
}
public IDisposable BeginDraw(IDrawingContextImpl ctx)
{
ctx.PushClip(_rect.ToRect(1));
_doubleRect = _rect.ToRect(1);
return Disposable.Create(ctx.PopClip);
}
public bool IsEmpty => _rect.Width == 0 | _rect.Height == 0;
public bool Intersects(Rect rect) => _doubleRect.Intersects(rect);
public bool Contains(Point pt) => _rect.Contains(PixelPoint.FromPoint(pt, 1));
public void Reset() => _rect = default;
public void Visualize(IDrawingContextImpl context)
{
context.DrawRectangle(
new ImmutableSolidColorBrush(
new Color(30, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))),
null, _doubleRect);
}
public PixelRect CombinedRect => _rect;
public IList<PixelRect> Rects
{
get
{
if (_rect.Width == 0 || _rect.Height == 0)
return Array.Empty<PixelRect>();
_rectsForApi[0] = _rect;
return _rectsForApi;
}
}
}
internal class RegionDirtyRectTracker : IDirtyRectTracker
{
private readonly IPlatformRenderInterfaceRegion _region;
private Random _random = new();
public RegionDirtyRectTracker(IPlatformRenderInterface platformRender)
{
_region = platformRender.CreateRegion();
}
public void AddRect(PixelRect rect) => _region.AddRect(rect);
public IDisposable BeginDraw(IDrawingContextImpl ctx)
{
ctx.PushClip(_region);
return Disposable.Create(ctx.PopClip);
}
public bool IsEmpty => _region.IsEmpty;
public bool Intersects(Rect rect) => _region.Intersects(rect);
public bool Contains(Point pt) => _region.Contains(pt);
public void Reset() => _region.Reset();
public void Visualize(IDrawingContextImpl context)
{
context.DrawRegion(
new ImmutableSolidColorBrush(
new Color(150, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))),
null, _region);
}
public PixelRect CombinedRect => _region.Bounds;
public IList<PixelRect> Rects => _region.Rects;
}

11
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@ -77,6 +77,9 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
_impl.DrawRectangle(brush, pen, rect, boxShadows);
}
public void DrawRegion(IBrush? brush, IPen? pen, IPlatformRenderInterfaceRegion region) =>
_impl.DrawRegion(brush, pen, region);
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
_impl.DrawEllipse(brush, pen, rect);
@ -87,7 +90,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
_impl.DrawGlyphRun(foreground, glyphRun);
}
public IDrawingContextLayerImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(PixelSize size)
{
return _impl.CreateLayer(size);
}
@ -102,11 +105,17 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
_impl.PushClip(clip);
}
public void PushClip(IPlatformRenderInterfaceRegion region) => _impl.PushClip(region);
public void PopClip()
{
_impl.PopClip();
}
public void PushLayer(Rect bounds) => _impl.PushLayer(bounds);
public void PopLayer() => _impl.PopLayer();
public void PushOpacity(double opacity, Rect? bounds)
{
_impl.PushOpacity(opacity, bounds);

13
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs

@ -16,26 +16,27 @@ namespace Avalonia.Rendering.Composition.Server
private Rect? _transformedContentBounds;
private IImmutableEffect? _oldEffect;
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
base.RenderCore(canvas, currentTransformedClip);
base.RenderCore(canvas, currentTransformedClip, dirtyRects);
foreach (var ch in Children)
{
ch.Render(canvas, currentTransformedClip);
ch.Render(canvas, currentTransformedClip, dirtyRects);
}
}
public override UpdateResult Update(ServerCompositionTarget root)
public override UpdateResult Update(ServerCompositionTarget root, Matrix parentCombinedTransform)
{
var (combinedBounds, oldInvalidated, newInvalidated) = base.Update(root);
var (combinedBounds, oldInvalidated, newInvalidated) = base.Update(root, parentCombinedTransform);
foreach (var child in Children)
{
if (child.AdornedVisual != null)
root.EnqueueAdornerUpdate(child);
else
{
var res = child.Update(root);
var res = child.Update(root, GlobalTransformMatrix);
oldInvalidated |= res.InvalidatedOld;
newInvalidated |= res.InvalidatedNew;
combinedBounds = Rect.Union(combinedBounds, res.Bounds);

6
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@ -40,13 +40,15 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
base.DeserializeChangesCore(reader, committedAt);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
if (_renderCommands != null)
{
_renderCommands.Render(canvas);
}
base.RenderCore(canvas, currentTransformedClip);
base.RenderCore(canvas, currentTransformedClip, dirtyRects);
}
public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated();

5
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionExperimentalAcrylicVisual.cs

@ -5,7 +5,8 @@ namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionExperimentalAcrylicVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
var cornerRadius = CornerRadius;
canvas.DrawRectangle(
@ -15,7 +16,7 @@ internal partial class ServerCompositionExperimentalAcrylicVisual
cornerRadius.TopLeft, cornerRadius.TopRight,
cornerRadius.BottomRight, cornerRadius.BottomLeft));
base.RenderCore(canvas, currentTransformedClip);
base.RenderCore(canvas, currentTransformedClip, dirtyRects);
}
public override Rect OwnContentBounds => new(0, 0, Size.X, Size.Y);

3
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs

@ -4,7 +4,8 @@ namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionSolidColorVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y));
}

3
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs

@ -4,7 +4,8 @@ namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionSurfaceVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
if (Surface == null)
return;

35
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.DirtyRects.cs

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections.Pooled;
namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionTarget
{
public readonly IDirtyRectTracker DirtyRects;
public void AddDirtyRect(Rect rect)
{
if (rect.Width == 0 && rect.Height == 0)
return;
var snapped = PixelRect.FromRect(SnapToDevicePixels(rect, Scaling), 1);
DebugEvents?.RectInvalidated(rect);
DirtyRects.AddRect(snapped);
_redrawRequested = true;
}
public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(rect, Scaling);
private static Rect SnapToDevicePixels(Rect rect, double scale)
{
return new Rect(
new Point(
Math.Floor(rect.X * scale) / scale,
Math.Floor(rect.Y * scale) / scale),
new Point(
Math.Ceiling(rect.Right * scale) / scale,
Math.Ceiling(rect.Bottom * scale) / scale));
}
}

121
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Avalonia.Collections.Pooled;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
@ -25,10 +26,9 @@ namespace Avalonia.Rendering.Composition.Server
private FpsCounter? _fpsCounter;
private FrameTimeGraph? _renderTimeGraph;
private FrameTimeGraph? _layoutTimeGraph;
private Rect _dirtyRect;
private readonly Random _random = new();
private Size _layerSize;
private PixelSize _layerSize;
private IDrawingContextLayerImpl? _layer;
private bool _updateRequested;
private bool _redrawRequested;
private bool _disposed;
private readonly HashSet<ServerCompositionVisual> _attachedVisuals = new();
@ -56,6 +56,11 @@ namespace Avalonia.Rendering.Composition.Server
_compositor = compositor;
_surfaces = surfaces;
_diagnosticTextRenderer = diagnosticTextRenderer;
var platformRender = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
DirtyRects = compositor.Options.UseRegionDirtyRectClipping == true &&
platformRender?.SupportsRegions == true
? new RegionDirtyRectTracker(platformRender)
: new DirtyRectTracker();
Id = Interlocked.Increment(ref s_nextId);
}
@ -146,7 +151,7 @@ namespace Avalonia.Rendering.Composition.Server
return;
}
if ((_dirtyRect.Width == 0 && _dirtyRect.Height == 0) && !_redrawRequested)
if (DirtyRects.IsEmpty && !_redrawRequested && !_updateRequested)
return;
Revision++;
@ -154,39 +159,51 @@ namespace Avalonia.Rendering.Composition.Server
var captureTiming = (DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0;
var startingTimestamp = captureTiming ? Stopwatch.GetTimestamp() : 0L;
var transform = Matrix.CreateScale(Scaling, Scaling);
// Update happens in a separate phase to extend dirty rect if needed
Root.Update(this);
Root.Update(this, transform);
while (_adornerUpdateQueue.Count > 0)
{
var adorner = _adornerUpdateQueue.Dequeue();
adorner.Update(this);
adorner.Update(this, transform);
}
_updateRequested = false;
Readback.CompleteWrite(Revision);
if (!_redrawRequested)
return;
_redrawRequested = false;
using (var targetContext = _renderTarget.CreateDrawingContext())
using (var targetContext = _renderTarget.CreateDrawingContext(false))
{
var size = Size;
var layerSize = size * Scaling;
if (layerSize != _layerSize || _layer == null || _layer.IsCorrupted)
if (PixelSize != _layerSize || _layer == null || _layer.IsCorrupted)
{
_layer?.Dispose();
_layer = null;
_layer = targetContext.CreateLayer(size);
_layerSize = layerSize;
_dirtyRect = new Rect(0, 0, size.Width, size.Height);
_layer = targetContext.CreateLayer(PixelSize);
_layerSize = PixelSize;
DirtyRects.AddRect(new PixelRect(_layerSize));
}
if (_dirtyRect.Width != 0 || _dirtyRect.Height != 0)
if (!DirtyRects.IsEmpty)
{
using (var context = _layer.CreateDrawingContext())
var useLayerClip = Compositor.Options.UseSaveLayerRootClip ??
Compositor.RenderInterface.GpuContext != null;
using (var context = _layer.CreateDrawingContext(false))
{
context.PushClip(_dirtyRect);
context.Clear(Colors.Transparent);
Root.Render(new CompositorDrawingContextProxy(context), _dirtyRect);
context.PopClip();
using (DirtyRects.BeginDraw(context))
{
context.Clear(Colors.Transparent);
if (useLayerClip)
context.PushLayer(DirtyRects.CombinedRect.ToRect(1));
Root.Render(new CompositorDrawingContextProxy(context), null, DirtyRects);
if (useLayerClip)
context.PopLayer();
}
}
}
@ -195,9 +212,10 @@ namespace Avalonia.Rendering.Composition.Server
if (_layer.CanBlit)
_layer.Blit(targetContext);
else
targetContext.DrawBitmap(_layer, 1,
new Rect(_layerSize),
new Rect(size));
{
var rect = new PixelRect(default, PixelSize).ToRect(1);
targetContext.DrawBitmap(_layer, 1, rect, rect);
}
if (DebugOverlays != RendererDebugOverlays.None)
{
@ -206,27 +224,24 @@ namespace Avalonia.Rendering.Composition.Server
var elapsed = StopwatchHelper.GetElapsedTime(startingTimestamp);
RenderTimeGraph?.AddFrameValue(elapsed.TotalMilliseconds);
}
DrawOverlays(targetContext);
DrawOverlays(targetContext, PixelSize.ToSize(Scaling));
}
RenderedVisuals = 0;
_dirtyRect = default;
DirtyRects.Reset();
}
}
private void DrawOverlays(IDrawingContextImpl targetContext)
private void DrawOverlays(IDrawingContextImpl targetContext, Size logicalSize)
{
if ((DebugOverlays & RendererDebugOverlays.DirtyRects) != 0)
{
targetContext.DrawRectangle(
new ImmutableSolidColorBrush(
new Color(30, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))),
null,
_dirtyRect);
}
if ((DebugOverlays & RendererDebugOverlays.DirtyRects) != 0)
DirtyRects.Visualize(targetContext);
targetContext.Transform = Matrix.CreateScale(Scaling, Scaling);
if ((DebugOverlays & RendererDebugOverlays.Fps) != 0)
{
var nativeMem = ByteSizeHelper.ToString((ulong) (
@ -247,9 +262,13 @@ namespace Avalonia.Rendering.Composition.Server
if (graph == null)
return;
top += 8.0;
targetContext.Transform = Matrix.CreateTranslation(Size.Width - graph.Size.Width - 8.0, top);
var oldTransform = targetContext.Transform;
targetContext.Transform = Matrix.CreateTranslation(logicalSize.Width - graph.Size.Width - 8.0, top) *
oldTransform;
graph.Render(targetContext);
top += graph.Size.Height;
targetContext.Transform = oldTransform;
}
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
@ -261,35 +280,11 @@ namespace Avalonia.Rendering.Composition.Server
{
DrawTimeGraph(RenderTimeGraph);
}
}
public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(rect, Scaling);
private static Rect SnapToDevicePixels(Rect rect, double scale)
{
return new Rect(
new Point(
Math.Floor(rect.X * scale) / scale,
Math.Floor(rect.Y * scale) / scale),
new Point(
Math.Ceiling(rect.Right * scale) / scale,
Math.Ceiling(rect.Bottom * scale) / scale));
targetContext.Transform = Matrix.Identity;
}
public void AddDirtyRect(Rect rect)
{
if (rect.Width == 0 && rect.Height == 0)
return;
var snapped = SnapToDevicePixels(rect, Scaling);
DebugEvents?.RectInvalidated(rect);
_dirtyRect = _dirtyRect.Union(snapped);
_redrawRequested = true;
}
public void Invalidate()
{
_redrawRequested = true;
}
public void RequestUpdate() => _updateRequested = true;
public void Dispose()
{

21
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -22,20 +22,25 @@ namespace Avalonia.Rendering.Composition.Server
private Rect? _transformedClipBounds;
private Rect _combinedTransformedClipBounds;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
}
public void Render(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
public void Render(CompositorDrawingContextProxy canvas, Rect? parentTransformedClip, IDirtyRectTracker dirtyRects)
{
if (Visible == false || IsVisibleInFrame == false)
return;
if (Opacity == 0)
return;
currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds);
var currentTransformedClip = parentTransformedClip.HasValue
? parentTransformedClip.Value.Intersect(_combinedTransformedClipBounds)
: _combinedTransformedClipBounds;
if (currentTransformedClip.Width == 0 && currentTransformedClip.Height == 0)
return;
if(!dirtyRects.Intersects(currentTransformedClip))
return;
Root!.RenderedVisuals++;
Root!.DebugEvents?.IncrementRenderedVisuals();
@ -67,7 +72,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.RenderOptions = RenderOptions;
RenderCore(canvas, currentTransformedClip);
RenderCore(canvas, currentTransformedClip, dirtyRects);
// Hack to force invalidation of SKMatrix
canvas.PostTransform = transform;
@ -116,7 +121,7 @@ namespace Avalonia.Rendering.Composition.Server
}
}
public virtual UpdateResult Update(ServerCompositionTarget root)
public virtual UpdateResult Update(ServerCompositionTarget root, Matrix parentVisualTransform)
{
if (Parent == null && Root == null)
return default;
@ -138,7 +143,7 @@ namespace Avalonia.Rendering.Composition.Server
_combinedTransformDirty = false;
}
var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix.Identity;
var parentTransform = AdornedVisual?.GlobalTransformMatrix ?? parentVisualTransform;
var newTransform = CombinedTransformMatrix * parentTransform;
@ -207,7 +212,7 @@ namespace Avalonia.Rendering.Composition.Server
_combinedTransformedClipBounds =
(AdornerIsClipped ? AdornedVisual?._combinedTransformedClipBounds : null)
?? (Parent?.Effect == null ? Parent?._combinedTransformedClipBounds : null)
?? new Rect(Root!.Size);
?? new Rect(Root!.PixelSize.ToSize(1));
if (_transformedClipBounds != null)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
@ -301,7 +306,7 @@ namespace Avalonia.Rendering.Composition.Server
protected override void ValuesInvalidated()
{
_isDirtyForUpdate = true;
Root?.Invalidate();
Root?.RequestUpdate();
}
public bool IsVisibleInFrame { get; set; }

1
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@ -38,6 +38,7 @@ namespace Avalonia.Rendering.Composition.Server
internal static readonly object RenderThreadDisposeStartMarker = new();
internal static readonly object RenderThreadJobsStartMarker = new();
internal static readonly object RenderThreadJobsEndMarker = new();
public CompositionOptions Options { get; } = AvaloniaLocator.Current.GetService<CompositionOptions>() ?? new();
public ServerCompositor(IRenderLoop renderLoop, IPlatformGraphics? platformGraphics,
BatchStreamObjectPool<object?> batchObjectPool, BatchStreamMemoryPool batchMemoryPool)

19
src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Logging;
using Avalonia.Media;
@ -16,15 +17,13 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
_handler.Attach(this);
}
protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
public void DispatchMessages(List<object> messages)
{
base.DeserializeChangesCore(reader, committedAt);
var count = reader.Read<int>();
for (var c = 0; c < count; c++)
foreach(var message in messages)
{
try
{
_handler.OnMessage(reader.ReadObject()!);
_handler.OnMessage(message);
}
catch (Exception e)
{
@ -58,6 +57,11 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
}
internal void HandlerInvalidate() => ValuesInvalidated();
internal void HandlerInvalidate(Rect rc)
{
Root?.AddDirtyRect(rc.TransformToAABB(GlobalTransformMatrix));
}
internal void HandlerRegisterForNextAnimationFrameUpdate()
{
@ -66,12 +70,13 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
Compositor.AddToClock(this);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
using var context = new ImmediateDrawingContext(canvas, false);
try
{
_handler.OnRender(context);
_handler.Render(context, currentTransformedClip);
}
catch (Exception e)
{

2
src/Avalonia.Base/composition-schema.xml

@ -54,7 +54,7 @@
<Property Name="DebugOverlays" Type="RendererDebugOverlays"/>
<Property Name="LastLayoutPassTiming" Type="LayoutPassTiming" Internal="true"/>
<Property Name="Scaling" Type="double"/>
<Property Name="Size" Type="Size" />
<Property Name="PixelSize" Type="PixelSize" />
</Object>
<KeyFrameAnimation Name="Scalar" Type="float"/>
<KeyFrameAnimation Name="Double" Type="double"/>

5
src/Avalonia.Controls/BorderVisual.cs

@ -45,7 +45,8 @@ class CompositionBorderVisual : CompositionDrawListVisual
{
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
if (ClipToBounds)
{
@ -56,7 +57,7 @@ class CompositionBorderVisual : CompositionDrawListVisual
canvas.PushClip(new RoundedRect(clipRect, _cornerRadius));
}
base.RenderCore(canvas, currentTransformedClip);
base.RenderCore(canvas, currentTransformedClip, dirtyRects);
if(ClipToBounds)
canvas.PopClip();

2
src/Avalonia.X11/X11CursorFactory.cs

@ -115,7 +115,7 @@ namespace Avalonia.X11
using (var cpuContext = platformRenderInterface.CreateBackendContext(null))
using (var renderTarget = cpuContext.CreateRenderTarget(new[] { this }))
using (var ctx = renderTarget.CreateDrawingContext())
using (var ctx = renderTarget.CreateDrawingContext(true))
{
var r = new Rect(_pixelSize.ToSize(1));
ctx.DrawBitmap(bitmap, 1, r, r);

2
src/Avalonia.X11/X11IconLoader.cs

@ -43,7 +43,7 @@ namespace Avalonia.X11
_bdata = new uint[_width * _height];
using(var cpuContext = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>().CreateBackendContext(null))
using(var rt = cpuContext.CreateRenderTarget(new[]{this}))
using (var ctx = rt.CreateDrawingContext())
using (var ctx = rt.CreateDrawingContext(true))
ctx.DrawBitmap(bitmap.PlatformImpl.Item, 1, new Rect(bitmap.Size),
new Rect(0, 0, _width, _height));
Data = new UIntPtr[_width * _height + 2];

26
src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -33,6 +33,8 @@ namespace Avalonia.Headless
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
public bool SupportsRegions => false;
public IPlatformRenderInterfaceRegion CreateRegion() => throw new NotSupportedException();
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new HeadlessGeometryStub(rect);
@ -398,7 +400,7 @@ namespace Avalonia.Headless
}
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool _)
{
return new HeadlessDrawingContextStub();
}
@ -454,7 +456,7 @@ namespace Avalonia.Headless
}
public IDrawingContextLayerImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(PixelSize size)
{
return new HeadlessBitmapStub(size, new Vector(96, 96));
}
@ -464,11 +466,24 @@ namespace Avalonia.Headless
}
public void PushClip(IPlatformRenderInterfaceRegion region)
{
}
public void PopClip()
{
}
public void PushLayer(Rect bounds)
{
}
public void PopLayer()
{
}
public void PushOpacity(double opacity, Rect? rect)
{
@ -541,6 +556,11 @@ namespace Avalonia.Headless
}
public void DrawRegion(IBrush? brush, IPen? pen, IPlatformRenderInterfaceRegion region)
{
}
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
}
@ -573,7 +593,7 @@ namespace Avalonia.Headless
}
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool _)
{
return new HeadlessDrawingContextStub();
}

95
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -21,7 +21,9 @@ namespace Avalonia.Skia
IDrawingContextImplWithEffects
{
private IDisposable?[]? _disposables;
private readonly Vector _dpi;
// TODO: Get rid of this value, it's currently used to calculate intermediate sizes for tile brushes
// but does so ignoring the current transform
private readonly Vector _intermediateSurfaceDpi;
private readonly Stack<PaintWrapper> _maskStack = new();
private readonly Stack<double> _opacityStack = new();
private readonly Stack<RenderOptions> _renderOptionsStack = new();
@ -57,7 +59,12 @@ namespace Avalonia.Skia
public SKSurface? Surface;
/// <summary>
/// Dpi of drawings.
/// Makes DPI to be applied as a hidden matrix transform
/// </summary>
public bool ScaleDrawingToDpi;
/// <summary>
/// Dpi for intermediate surfaces
/// </summary>
public Vector Dpi;
@ -180,7 +187,7 @@ namespace Avalonia.Skia
Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas
?? throw new ArgumentException("Invalid create info - no Canvas provided", nameof(createInfo));
_dpi = createInfo.Dpi;
_intermediateSurfaceDpi = createInfo.Dpi;
_disposables = disposables;
_disableSubpixelTextRendering = createInfo.DisableSubpixelTextRendering;
_grContext = createInfo.GrContext;
@ -191,10 +198,12 @@ namespace Avalonia.Skia
_session = createInfo.CurrentSession;
if (!_dpi.NearlyEquals(SkiaPlatform.DefaultDpi))
if (createInfo.ScaleDrawingToDpi && !createInfo.Dpi.NearlyEquals(SkiaPlatform.DefaultDpi))
{
_postTransform =
Matrix.CreateScale(_dpi.X / SkiaPlatform.DefaultDpi.X, _dpi.Y / SkiaPlatform.DefaultDpi.Y);
Matrix.CreateScale(createInfo.Dpi.X / SkiaPlatform.DefaultDpi.X,
createInfo.Dpi.Y / SkiaPlatform.DefaultDpi.Y);
}
Transform = Matrix.Identity;
@ -526,6 +535,32 @@ namespace Avalonia.Skia
SKRoundRectCache.Shared.Return(skRoundRect);
}
/// <inheritdoc />
public void DrawRegion(IBrush? brush, IPen? pen, IPlatformRenderInterfaceRegion region)
{
var r = (SkiaRegionImpl)region;
if(r.IsEmpty)
return;
CheckLease();
if (brush != null)
{
using (var fill = CreatePaint(_fillPaint, brush, r.Bounds.ToRect(1)))
{
Canvas.DrawRegion(r.Region, fill.Paint);
}
}
if (pen is not null
&& TryCreatePaint(_strokePaint, pen, r.Bounds.ToRect(1).Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
{
using (stroke)
{
Canvas.DrawRegion(r.Region, stroke.Paint);
}
}
}
/// <inheritdoc />
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
@ -591,7 +626,7 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public IDrawingContextLayerImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(PixelSize size)
{
CheckLease();
return CreateRenderTarget(size, true);
@ -629,6 +664,14 @@ namespace Avalonia.Skia
SKRoundRectCache.Shared.Return(roundRect);
}
public void PushClip(IPlatformRenderInterfaceRegion region)
{
var r = ((SkiaRegionImpl)region).Region;
CheckLease();
Canvas.Save();
Canvas.ClipRegion(r);
}
/// <inheritdoc />
public void PopClip()
{
@ -636,6 +679,18 @@ namespace Avalonia.Skia
Canvas.Restore();
}
public void PushLayer(Rect bounds)
{
CheckLease();
Canvas.SaveLayer(bounds.ToSKRect(), null!);
}
public void PopLayer()
{
CheckLease();
Canvas.Restore();
}
/// <inheritdoc />
public void PushOpacity(double opacity, Rect? bounds)
{
@ -976,15 +1031,16 @@ namespace Avalonia.Skia
/// <param name="tileBrushImage">Tile brush image.</param>
private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Rect targetBox, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage)
{
var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSizeWithDpi(_dpi), targetBox.Size);
var intermediate = CreateRenderTarget(calc.IntermediateSize, false);
var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSizeWithDpi(_intermediateSurfaceDpi), targetBox.Size);
var intermediate = CreateRenderTarget(
PixelSize.FromSizeWithDpi(calc.IntermediateSize, _intermediateSurfaceDpi), false);
paintWrapper.AddDisposable(intermediate);
using (var context = intermediate.CreateDrawingContext())
using (var context = intermediate.CreateDrawingContext(true))
{
var sourceRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(96));
var targetRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(_dpi));
var targetRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(_intermediateSurfaceDpi));
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
@ -1028,7 +1084,7 @@ namespace Avalonia.Skia
SKMatrix.Concat(
ref paintTransform,
tileTransform,
SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
SKMatrix.CreateScale((float)(96.0 / _intermediateSurfaceDpi.X), (float)(96.0 / _intermediateSurfaceDpi.Y)));
if (tileBrush.Transform is { })
{
@ -1066,9 +1122,10 @@ namespace Avalonia.Skia
if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
{
using var intermediate = CreateRenderTarget(intermediateSize, false);
using var intermediate = CreateRenderTarget(
PixelSize.FromSizeWithDpi(intermediateSize, _intermediateSurfaceDpi), false);
using (var ctx = intermediate.CreateDrawingContext())
using (var ctx = intermediate.CreateDrawingContext(true))
{
ctx.RenderOptions = RenderOptions;
ctx.Clear(Colors.Transparent);
@ -1096,7 +1153,7 @@ namespace Avalonia.Skia
var calc = new TileBrushCalculator(tileBrush, contentSize, targetRect.Size);
transform *= calc.IntermediateTransform;
using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi);
using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _intermediateSurfaceDpi);
using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize))
{
ctx.RenderOptions = RenderOptions;
@ -1127,7 +1184,7 @@ namespace Avalonia.Skia
: SKShaderTileMode.Repeat;
paintTransform = SKMatrix.Concat(paintTransform,
SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
SKMatrix.CreateScale((float)(96.0 / _intermediateSurfaceDpi.X), (float)(96.0 / _intermediateSurfaceDpi.Y)));
if (tileBrush.Transform is { })
{
@ -1339,18 +1396,18 @@ namespace Avalonia.Skia
/// <summary>
/// Create new render target compatible with this drawing context.
/// </summary>
/// <param name="size">The size of the render target in DIPs.</param>
/// <param name="pixelSize">The size of the render target.</param>
/// <param name="dpi">The DPI of the render target.</param>
/// <param name="isLayer">Whether the render target is being created for a layer.</param>
/// <param name="format">Pixel format.</param>
/// <returns></returns>
private SurfaceRenderTarget CreateRenderTarget(Size size, bool isLayer, PixelFormat? format = null)
private SurfaceRenderTarget CreateRenderTarget(PixelSize pixelSize, bool isLayer, PixelFormat? format = null)
{
var pixelSize = PixelSize.FromSizeWithDpi(size, _dpi);
var createInfo = new SurfaceRenderTarget.CreateInfo
{
Width = pixelSize.Width,
Height = pixelSize.Height,
Dpi = _dpi,
Dpi = _intermediateSurfaceDpi,
Format = format,
DisableTextLcdRendering = isLayer ? _disableSubpixelTextRendering : true,
GrContext = _grContext,

5
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -37,7 +37,7 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool scaleDrawingToDpi)
{
if (_renderTarget == null)
throw new ObjectDisposedException(nameof(FramebufferRenderTarget));
@ -58,7 +58,8 @@ namespace Avalonia.Skia
var createInfo = new DrawingContextImpl.CreateInfo
{
Surface = _framebufferSurface,
Dpi = framebuffer.Dpi
Dpi = framebuffer.Dpi,
ScaleDrawingToDpi = scaleDrawingToDpi
};
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer);

3
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -21,7 +21,7 @@ namespace Avalonia.Skia
_renderTarget.Dispose();
}
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
{
var session = _renderTarget.BeginRenderingSession();
@ -30,6 +30,7 @@ namespace Avalonia.Skia
GrContext = session.GrContext,
Surface = session.SkSurface,
Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
ScaleDrawingToDpi = useScaledDrawing,
Gpu = _skiaGpu,
CurrentSession = session
};

1
src/Skia/Avalonia.Skia/PictureRenderTarget.cs

@ -38,6 +38,7 @@ internal class PictureRenderTarget : IDisposable
var createInfo = new DrawingContextImpl.CreateInfo
{
Canvas = canvas,
ScaleDrawingToDpi = true,
Dpi = _dpi,
DisableSubpixelTextRendering = true,
GrContext = _grContext,

3
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -50,6 +50,9 @@ namespace Avalonia.Skia
|| format == PixelFormats.Bgra8888
|| format == PixelFormats.Rgba8888;
public bool SupportsRegions => true;
public IPlatformRenderInterfaceRegion CreateRegion() => new SkiaRegionImpl();
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);

3
src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs

@ -19,7 +19,8 @@ internal class RenderTargetBitmapImpl : WriteableBitmapImpl,
_renderTarget = new FramebufferRenderTarget(this);
}
public IDrawingContextImpl CreateDrawingContext() => _renderTarget.CreateDrawingContext();
IDrawingContextImpl IRenderTarget.CreateDrawingContext(bool useScaledDrawing) =>
_renderTarget.CreateDrawingContext(useScaledDrawing);
public bool IsCorrupted => false;

53
src/Skia/Avalonia.Skia/SkiaRegionImpl.cs

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia;
internal class SkiaRegionImpl : IPlatformRenderInterfaceRegion
{
private SKRegion? _region = new();
public SKRegion Region => _region ?? throw new ObjectDisposedException(nameof(SkiaRegionImpl));
private bool _rectsValid;
private List<PixelRect>? _rects;
public void Dispose()
{
_region?.Dispose();
_region = null;
}
public void AddRect(PixelRect rect)
{
_rectsValid = false;
Region.Op(rect.X, rect.Y, rect.Right, rect.Bottom, SKRegionOperation.Union);
}
public void Reset()
{
_rectsValid = false;
Region.SetEmpty();
}
public bool IsEmpty => Region.IsEmpty;
public PixelRect Bounds => Region.Bounds.ToAvaloniaPixelRect();
public IList<PixelRect> Rects
{
get
{
_rects ??= new();
if (!_rectsValid)
{
_rects.Clear();
using var iter = Region.CreateRectIterator();
while (iter.Next(out var rc))
_rects.Add(rc.ToAvaloniaPixelRect());
}
return _rects;
}
}
public bool Intersects(Rect rect) => Region.Intersects(PixelRect.FromRect(rect, 1).ToSKRectI());
public bool Contains(Point pt) => Region.Contains((int)pt.X, (int)pt.Y);
}

10
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@ -75,6 +75,11 @@ namespace Avalonia.Skia
{
return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom);
}
public static SKRectI ToSKRectI(this PixelRect r)
{
return new SKRectI(r.X, r.Y, r.Right, r.Bottom);
}
public static SKRoundRect ToSKRoundRect(this RoundedRect r)
{
@ -95,6 +100,11 @@ namespace Avalonia.Skia
{
return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
}
public static PixelRect ToAvaloniaPixelRect(this SKRectI r)
{
return new PixelRect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
}
public static SKMatrix ToSKMatrix(this Matrix m)
{

3
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -97,7 +97,7 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
{
_canvas.RestoreToCount(-1);
_canvas.ResetMatrix();
@ -106,6 +106,7 @@ namespace Avalonia.Skia
{
Surface = _surface.Surface,
Dpi = Dpi,
ScaleDrawingToDpi = useScaledDrawing,
DisableSubpixelTextRendering = _disableLcdRendering,
GrContext = _grContext,
Gpu = _gpu,

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

@ -296,5 +296,8 @@ namespace Avalonia.Direct2D1
public bool IsSupportedBitmapPixelFormat(PixelFormat format) =>
format == PixelFormats.Bgra8888
|| format == PixelFormats.Rgba8888;
public bool SupportsRegions => false;
public IPlatformRenderInterfaceRegion CreateRegion() => throw new NotSupportedException();
}
}

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

@ -21,11 +21,11 @@ namespace Avalonia.Direct2D1
_externalRenderTargetProvider.DestroyRenderTarget();
}
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
_externalRenderTargetProvider.BeforeDrawing();
return new DrawingContextImpl( null, target, null, () =>
return new DrawingContextImpl( null, target, useScaledDrawing, null, () =>
{
try
{

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

@ -25,7 +25,7 @@ namespace Avalonia.Direct2D1
_target = null;
}
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
{
if (_target == null)
throw new ObjectDisposedException(nameof(FramebufferShimRenderTarget));
@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1
}
return new FramebufferShim(locked)
.CreateDrawingContext();
.CreateDrawingContext(useScaledDrawing);
}
public bool IsCorrupted => false;
@ -52,9 +52,9 @@ namespace Avalonia.Direct2D1
_target = target;
}
public override IDrawingContextImpl CreateDrawingContext()
public override IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
{
return base.CreateDrawingContext(() =>
return base.CreateDrawingContext(useScaledDrawing, () =>
{
using (var l = WicImpl.Lock(BitmapLockFlags.Read))
{

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

@ -39,6 +39,7 @@ namespace Avalonia.Direct2D1.Media
public DrawingContextImpl(
ILayerFactory layerFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
bool useScaledDrawing,
SharpDX.DXGI.SwapChain1 swapChain = null,
Action finishedCallback = null)
{
@ -58,6 +59,13 @@ namespace Avalonia.Direct2D1.Media
_ownsDeviceContext = true;
}
if (!useScaledDrawing)
{
var scaling = _renderTarget.DotsPerInch.Width / 96;
if (!MathUtilities.AreClose(1, scaling))
_postTransform = Matrix.CreateScale(1 / scaling, 1 / scaling);
}
_deviceContext.BeginDraw();
}
@ -66,8 +74,13 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public Matrix Transform
{
get { return _deviceContext.Transform.ToAvalonia(); }
set { _deviceContext.Transform = value.ToDirect2D(); }
get { return _transform; }
set
{
_transform = value;
_deviceContext.Transform =
(_postTransform.HasValue ? value * _postTransform.Value : value).ToDirect2D();
}
}
public Matrix4x4 Transform4x4
@ -353,6 +366,11 @@ namespace Avalonia.Direct2D1.Media
}
}
public void DrawRegion(IBrush brush, IPen pen, IPlatformRenderInterfaceRegion region)
{
throw new NotSupportedException();
}
/// <inheritdoc />
public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
{
@ -410,17 +428,17 @@ namespace Avalonia.Direct2D1.Media
}
}
public IDrawingContextLayerImpl CreateLayer(Size size)
public IDrawingContextLayerImpl CreateLayer(PixelSize pixelSize)
{
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
if (_layerFactory != null)
{
return _layerFactory.CreateLayer(size);
return _layerFactory.CreateLayer(pixelSize.ToSizeWithDpi(dpi));
}
else
{
var platform = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
var pixelSize = PixelSize.FromSizeWithDpi(size, dpi);
return (IDrawingContextLayerImpl)platform.CreateRenderTargetBitmap(pixelSize, dpi);
}
}
@ -441,14 +459,40 @@ namespace Avalonia.Direct2D1.Media
_deviceContext.PushAxisAlignedClip(clip.Rect.ToDirect2D(), AntialiasMode.PerPrimitive);
}
public void PushClip(IPlatformRenderInterfaceRegion region)
{
throw new NotSupportedException();
}
public void PopClip()
{
_deviceContext.PopAxisAlignedClip();
}
public void PushLayer(Rect bounds)
{
var parameters = new LayerParameters
{
ContentBounds = bounds.ToDirect2D(),
MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
Opacity = 1
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);
_layers.Push(layer);
}
void IDrawingContextImpl.PopLayer()
{
PopLayer();
}
readonly Stack<Layer> _layers = new Stack<Layer>();
private readonly Stack<Layer> _layerPool = new Stack<Layer>();
private RenderOptions _renderOptions;
private readonly Matrix? _postTransform;
private Matrix _transform = Matrix.Identity;
/// <summary>
/// Pushes an opacity value.
@ -574,7 +618,7 @@ namespace Avalonia.Direct2D1.Media
CompatibleRenderTargetOptions.None,
pixelSize.ToSizeWithDpi(dpi).ToSharpDX()))
{
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(true))
{
intermediate.Clear(null);
sceneBrushContent.Render(ctx,

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

@ -98,7 +98,7 @@ namespace Avalonia.Direct2D1.Media
CompatibleRenderTargetOptions.None,
calc.IntermediateSize.ToSharpDX());
using (var context = new RenderTarget(result).CreateDrawingContext())
using (var context = new RenderTarget(result).CreateDrawingContext(true))
{
var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height);
var rect = new Rect(bitmap.PixelSize.ToSizeWithDpi(dpi));

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

@ -30,9 +30,10 @@ namespace Avalonia.Direct2D1.Media.Imaging
return new D2DRenderTargetBitmapImpl(bitmapRenderTarget);
}
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
{
return new DrawingContextImpl( this, _renderTarget, null, () => Version++);
return new DrawingContextImpl( this, _renderTarget, useScaledDrawing,
null, () => Version++);
}
public bool IsCorrupted => false;
@ -60,7 +61,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
using (var wic = new WicRenderTargetBitmapImpl(PixelSize, Dpi))
{
using (var dc = wic.CreateDrawingContext(null))
using (var dc = wic.CreateDrawingContext(true, null))
{
dc.DrawBitmap(
this,

8
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -34,14 +34,14 @@ namespace Avalonia.Direct2D1.Media
base.Dispose();
}
public virtual IDrawingContextImpl CreateDrawingContext()
=> CreateDrawingContext(null);
public virtual IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
=> CreateDrawingContext(useScaledDrawing, null);
public bool IsCorrupted => false;
public IDrawingContextImpl CreateDrawingContext(Action finishedCallback)
public IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing, Action finishedCallback)
{
return new DrawingContextImpl(null, _renderTarget, finishedCallback: () =>
return new DrawingContextImpl(null, _renderTarget, useScaledDrawing, finishedCallback: () =>
{
Version++;
finishedCallback?.Invoke();

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

@ -25,9 +25,9 @@ namespace Avalonia.Direct2D1
/// Creates a drawing context for a rendering session.
/// </summary>
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
{
return new DrawingContextImpl(this, _renderTarget);
return new DrawingContextImpl(this, _renderTarget, useScaledDrawing);
}
public bool IsCorrupted => false;

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

@ -19,7 +19,7 @@ namespace Avalonia.Direct2D1
/// Creates a drawing context for a rendering session.
/// </summary>
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(bool useScaledDrawing)
{
var size = GetWindowSize();
var dpi = GetWindowDpi();
@ -32,7 +32,7 @@ namespace Avalonia.Direct2D1
Resize();
}
return new DrawingContextImpl(this, _deviceContext, _swapChain);
return new DrawingContextImpl(this, _deviceContext, useScaledDrawing, _swapChain);
}
public bool IsCorrupted => false;

2
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@ -72,7 +72,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
var r = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
using(var cpuContext = r.CreateBackendContext(null))
using (var target = cpuContext.CreateRenderTarget(new object[] { fb }))
using (var ctx = target.CreateDrawingContext())
using (var ctx = target.CreateDrawingContext(false))
{
ctx.Clear(Colors.Transparent);
ctx.PushOpacity(0.8, new Rect(0, 0, 80, 80));

6
tests/Avalonia.UnitTests/TestRoot.cs

@ -79,16 +79,16 @@ namespace Avalonia.UnitTests
public IRenderTarget CreateRenderTarget()
{
var dc = new Mock<IDrawingContextImpl>();
dc.Setup(x => x.CreateLayer(It.IsAny<Size>())).Returns(() =>
dc.Setup(x => x.CreateLayer(It.IsAny<PixelSize>())).Returns(() =>
{
var layerDc = new Mock<IDrawingContextImpl>();
var layer = new Mock<IDrawingContextLayerImpl>();
layer.Setup(x => x.CreateDrawingContext()).Returns(layerDc.Object);
layer.Setup(x => x.CreateDrawingContext(It.IsAny<bool>())).Returns(layerDc.Object);
return layer.Object;
});
var result = new Mock<IRenderTarget>();
result.Setup(x => x.CreateDrawingContext()).Returns(dc.Object);
result.Setup(x => x.CreateDrawingContext(It.IsAny<bool>())).Returns(dc.Object);
return result.Object;
}

Loading…
Cancel
Save