diff --git a/src/Avalonia.Base/Media/BitmapCache.cs b/src/Avalonia.Base/Media/BitmapCache.cs
index c96c95bedd..94caa1f557 100644
--- a/src/Avalonia.Base/Media/BitmapCache.cs
+++ b/src/Avalonia.Base/Media/BitmapCache.cs
@@ -3,12 +3,15 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Media;
+///
+/// Represents the behavior of caching a visual element or tree of elements as bitmap surfaces.
+///
public class BitmapCache : CacheMode
{
private CompositionBitmapCache? _current;
public static readonly StyledProperty RenderAtScaleProperty = AvaloniaProperty.Register(
- "RenderAtScale", 1);
+ nameof(RenderAtScale), 1);
///
/// Use the RenderAtScale property to render the BitmapCache at a multiple of the normal bitmap size.
@@ -31,7 +34,7 @@ public class BitmapCache : CacheMode
}
public static readonly StyledProperty SnapsToDevicePixelsProperty = AvaloniaProperty.Register(
- "SnapsToDevicePixels");
+ nameof(SnapsToDevicePixels));
///
/// Set the SnapsToDevicePixels property when the cache displays content that requires pixel-alignment to render correctly.
@@ -55,8 +58,22 @@ public class BitmapCache : CacheMode
}
public static readonly StyledProperty EnableClearTypeProperty = AvaloniaProperty.Register(
- "EnableClearType");
+ nameof(EnableClearType));
+ ///
+ /// Set the EnableClearType property to allow subpixel text to be rendered in the cache.
+ /// When the EnableClearType property is true, your application MUST render all
+ /// of its subpixel text on an opaque background.
+ ///
+ /// When the EnableClearType property is false, text in the cache is rendered with grayscale antialiasing.
+ ///
+ /// ClearType text requires correct pixel alignment of rendered characters,
+ /// so you should set the SnapsToDevicePixels property to true.
+ /// If you do not set this property, the content may not blend correctly.
+ ///
+ /// Use the EnableClearType property when you know the cache is rendered on pixel boundaries,
+ /// so it is safe to cache ClearType text. This situation occurs commonly in text-scrolling scenarios.
+ ///
public bool EnableClearType
{
get => GetValue(EnableClearTypeProperty);
diff --git a/src/Avalonia.Base/Media/CacheMode.cs b/src/Avalonia.Base/Media/CacheMode.cs
index 76be3e3579..44784eb29c 100644
--- a/src/Avalonia.Base/Media/CacheMode.cs
+++ b/src/Avalonia.Base/Media/CacheMode.cs
@@ -4,6 +4,9 @@ using Avalonia.Rendering.Composition.Drawing;
namespace Avalonia.Media;
+///
+/// Represents cached content modes for graphics acceleration features.
+///
public abstract class CacheMode : StyledElement
{
// We currently only allow visual to be attached to one compositor at a time, so keep it simple for now
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionOptions.cs b/src/Avalonia.Base/Rendering/Composition/CompositionOptions.cs
index 4c9a693b0f..f474f9382d 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionOptions.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionOptions.cs
@@ -17,6 +17,11 @@ public class CompositionOptions
///
public int? MaxDirtyRects { get; set; }
+
+ ///
+ /// Controls the eagerness of merging dirty rects. WPF uses 50000, Avalonia currently has a different default
+ /// that's a subject to change. You can play with this property to find the best value for your application.
+ ///
[Unstable]
public double? DirtyRectMergeEagerness { get; set; }
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DirtyRects/MultiDirtyRectTracker.CDirtyRegion.cs b/src/Avalonia.Base/Rendering/Composition/Server/DirtyRects/MultiDirtyRectTracker.CDirtyRegion.cs
index d4d993b531..d89ca0a9f0 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DirtyRects/MultiDirtyRectTracker.CDirtyRegion.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DirtyRects/MultiDirtyRectTracker.CDirtyRegion.cs
@@ -17,7 +17,6 @@ partial class MultiDirtyRectTracker
private readonly double[,] _overhead = new double[MaxDirtyRegionCount + 1, MaxDirtyRegionCount];
private LtrbRect _surfaceBounds;
private double _allowedDirtyRegionOverhead;
- private double _accumulatedOverhead;
private int _regionCount;
private bool _optimized;
private bool _maxSurfaceFallback;
@@ -25,6 +24,7 @@ partial class MultiDirtyRectTracker
private readonly struct UnionResult
{
public readonly double Overhead;
+ // Left here for debugging purposes
public readonly double Area;
public readonly LtrbRect Union;
@@ -120,7 +120,6 @@ partial class MultiDirtyRectTracker
_allowedDirtyRegionOverhead = allowedDirtyRegionOverhead;
Array.Clear(_dirtyRegions);
Array.Clear(_overhead);
- _accumulatedOverhead = 0;
_optimized = false;
_maxSurfaceFallback = false;
_regionCount = 0;
@@ -236,7 +235,6 @@ partial class MultiDirtyRectTracker
return;
}
- _accumulatedOverhead += ur.Overhead;
_dirtyRegions[bestMatchK] = unioned;
UpdateOverhead(bestMatchK);
}
@@ -244,7 +242,6 @@ partial class MultiDirtyRectTracker
{
// Case B: Merge region N with region K, store new region slot K
var ur = ComputeUnion(_dirtyRegions[bestMatchN], _dirtyRegions[bestMatchK]);
- _accumulatedOverhead += ur.Overhead;
_dirtyRegions[bestMatchN] = ur.Union;
_dirtyRegions[bestMatchK] = clippedNewRegion;
UpdateOverhead(bestMatchN);
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
index cf04ef52c1..19a5f0e881 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
@@ -31,7 +31,7 @@ namespace Avalonia.Rendering.Composition.Server
private bool _fullRedrawRequested;
private bool _disposed;
private readonly HashSet _attachedVisuals = new();
- public readonly IDirtyRectTracker DirtyRects;
+ public IDirtyRectTracker DirtyRects { get; }
public long Id { get; }
public ulong Revision { get; private set; }
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Act.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Act.cs
index 073a7a4bef..c0fd5ab777 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Act.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Act.cs
@@ -5,13 +5,13 @@ namespace Avalonia.Rendering.Composition.Server;
partial class ServerCompositionVisual
{
- // ACT = Ancestor Transform Tracker
+ // ATT = Ancestor Transform Tracker
// While we generally avoid dealing with keeping world transforms up to date,
// we still need it for cases like adorners.
// Instead of updating world transforms eagerly, we use a subscription model where
// visuals can subscribe to notifications when any ancestor's world transform changes.
- class ActHelper
+ class AttHelper
{
public readonly HashSet AncestorChainTransformSubscribers = new();
public required Action ParentActSubscriptionAction;
@@ -21,51 +21,51 @@ partial class ServerCompositionVisual
public bool EnqueuedForAdornerUpdate;
}
- private ActHelper? _actHelper;
+ private AttHelper? _AttHelper;
- private ActHelper GetActHelper() => _actHelper ??= new()
+ private AttHelper GetAttHelper() => _AttHelper ??= new()
{
- ParentActSubscriptionAction = ActHelper_CombinedTransformChanged,
- AdornedVisualActSubscriptionAction = ActHelper_OnAdornedVisualWorldTransformChanged
+ ParentActSubscriptionAction = AttHelper_CombinedTransformChanged,
+ AdornedVisualActSubscriptionAction = AttHelper_OnAdornedVisualWorldTransformChanged
};
- private void ActHelper_CombinedTransformChanged()
+ private void AttHelper_CombinedTransformChanged()
{
- if(_actHelper == null || _actHelper.AncestorChainTransformSubscribers.Count == 0)
+ if(_AttHelper == null || _AttHelper.AncestorChainTransformSubscribers.Count == 0)
return;
- foreach (var sub in _actHelper.AncestorChainTransformSubscribers)
+ foreach (var sub in _AttHelper.AncestorChainTransformSubscribers)
sub();
}
- private void ActHelper_ParentChanging()
+ private void AttHelper_ParentChanging()
{
- if(Parent != null && _actHelper?.AncestorChainTransformSubscribers.Count > 0)
- Parent.ActHelper_UnsubscribeFromActNotification(_actHelper.ParentActSubscriptionAction);
+ if(Parent != null && _AttHelper?.AncestorChainTransformSubscribers.Count > 0)
+ Parent.AttHelper_UnsubscribeFromActNotification(_AttHelper.ParentActSubscriptionAction);
}
- private void ActHelper_ParentChanged()
+ private void AttHelper_ParentChanged()
{
- if(Parent != null && _actHelper?.AncestorChainTransformSubscribers.Count > 0)
- Parent.ActHelper_SubscribeToActNotification(_actHelper.ParentActSubscriptionAction);
+ if(Parent != null && _AttHelper?.AncestorChainTransformSubscribers.Count > 0)
+ Parent.AttHelper_SubscribeToActNotification(_AttHelper.ParentActSubscriptionAction);
if(Parent != null && AdornedVisual != null)
AdornerHelper_EnqueueForAdornerUpdate();
}
- protected void ActHelper_SubscribeToActNotification(Action cb)
+ protected void AttHelper_SubscribeToActNotification(Action cb)
{
- var h = GetActHelper();
+ var h = GetAttHelper();
(h.AncestorChainTransformSubscribers).Add(cb);
if (h.AncestorChainTransformSubscribers.Count == 1)
- Parent?.ActHelper_SubscribeToActNotification(h.ParentActSubscriptionAction);
+ Parent?.AttHelper_SubscribeToActNotification(h.ParentActSubscriptionAction);
}
- protected void ActHelper_UnsubscribeFromActNotification(Action cb)
+ protected void AttHelper_UnsubscribeFromActNotification(Action cb)
{
- var h = GetActHelper();
+ var h = GetAttHelper();
h.AncestorChainTransformSubscribers.Remove(cb);
if(h.AncestorChainTransformSubscribers.Count == 0)
- Parent?.ActHelper_UnsubscribeFromActNotification(h.ParentActSubscriptionAction);
+ Parent?.AttHelper_UnsubscribeFromActNotification(h.ParentActSubscriptionAction);
}
protected static bool ComputeTransformFromAncestor(ServerCompositionVisual visual,
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Adorners.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Adorners.cs
index 228530f570..6f5f7b7572 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Adorners.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Adorners.cs
@@ -9,7 +9,7 @@ partial class ServerCompositionVisual
// Support for adorners is a rather cancerou^W invasive thing, so we isolate all related code in this file
// and prefix it with AdornerHelper_.
- private void ActHelper_OnAdornedVisualWorldTransformChanged() => AdornerHelper_EnqueueForAdornerUpdate();
+ private void AttHelper_OnAdornedVisualWorldTransformChanged() => AdornerHelper_EnqueueForAdornerUpdate();
private void AdornerHelper_AttachedToRoot()
{
@@ -19,7 +19,7 @@ partial class ServerCompositionVisual
public void AdornerHelper_EnqueueForAdornerUpdate()
{
- var helper = GetActHelper();
+ var helper = GetAttHelper();
if(helper.EnqueuedForAdornerUpdate)
return;
Compositor.EnqueueAdornerUpdate(this);
@@ -27,11 +27,11 @@ partial class ServerCompositionVisual
}
partial void OnAdornedVisualChanging() =>
- AdornedVisual?.ActHelper_UnsubscribeFromActNotification(GetActHelper().AdornedVisualActSubscriptionAction);
+ AdornedVisual?.AttHelper_UnsubscribeFromActNotification(GetAttHelper().AdornedVisualActSubscriptionAction);
partial void OnAdornedVisualChanged()
{
- AdornedVisual?.ActHelper_SubscribeToActNotification(GetActHelper().AdornedVisualActSubscriptionAction);
+ AdornedVisual?.AttHelper_SubscribeToActNotification(GetAttHelper().AdornedVisualActSubscriptionAction);
AdornerHelper_EnqueueForAdornerUpdate();
}
@@ -45,7 +45,7 @@ partial class ServerCompositionVisual
public void UpdateAdorner()
{
- GetActHelper().EnqueuedForAdornerUpdate = false;
+ GetAttHelper().EnqueuedForAdornerUpdate = false;
var ownTransform = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix, Scale,
RotationAngle, Orientation, Offset);
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.ComputedProperties.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.ComputedProperties.cs
index 7a9ed98787..4b98b0f80e 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.ComputedProperties.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.ComputedProperties.cs
@@ -87,27 +87,27 @@ partial class ServerCompositionVisual
// ReplaceChild | Y | Y | Y(N)
// -----------------+---------------+-----------------+-----------------------
// RemoveChild | Y | Y | Y(N)
- private void PropagateFlags(bool fNeedsBoundingBoxUpdate, bool fDirtyForRender, bool fAdditionalDirtyRegion = false)
+ private void PropagateFlags(bool needsBoundingBoxUpdate, bool dirtyForRender, bool additionalDirtyRegion = false)
{
Root?.RequestUpdate();
var parent = Parent;
- var setIsDirtyForRenderInSubgraph = fAdditionalDirtyRegion || fDirtyForRender;
+ var setIsDirtyForRenderInSubgraph = additionalDirtyRegion || dirtyForRender;
while (parent != null &&
- ((fNeedsBoundingBoxUpdate && !parent._needsBoundingBoxUpdate) ||
+ ((needsBoundingBoxUpdate && !parent._needsBoundingBoxUpdate) ||
(setIsDirtyForRenderInSubgraph && !parent._isDirtyForRenderInSubgraph)))
{
- parent._needsBoundingBoxUpdate |= fNeedsBoundingBoxUpdate;
+ parent._needsBoundingBoxUpdate |= needsBoundingBoxUpdate;
parent._isDirtyForRenderInSubgraph |= setIsDirtyForRenderInSubgraph;
parent = parent.Parent;
}
- _needsBoundingBoxUpdate |= fNeedsBoundingBoxUpdate;
- _isDirtyForRender |= fDirtyForRender;
+ _needsBoundingBoxUpdate |= needsBoundingBoxUpdate;
+ _isDirtyForRender |= dirtyForRender;
// If node itself is dirty for render, we don't need to keep track of extra dirty rects
- _hasExtraDirtyRect = !fDirtyForRender && (_hasExtraDirtyRect || fAdditionalDirtyRegion);
+ _hasExtraDirtyRect = !dirtyForRender && (_hasExtraDirtyRect || additionalDirtyRegion);
}
public void RecomputeOwnProperties()
@@ -152,7 +152,7 @@ partial class ServerCompositionVisual
setDirtyForRender = setDirtyBounds = true;
- ActHelper_CombinedTransformChanged();
+ AttHelper_CombinedTransformChanged();
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.DirtyInputs.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.DirtyInputs.cs
index 75f0185b84..fa8c6047fc 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.DirtyInputs.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.DirtyInputs.cs
@@ -148,7 +148,7 @@ partial class ServerCompositionVisual
{
if (Parent != null && _transformedSubTreeBounds.HasValue)
Parent.AddExtraDirtyRect(_transformedSubTreeBounds.Value);
- ActHelper_ParentChanging();
+ AttHelper_ParentChanging();
}
partial void OnParentChanged()
@@ -158,7 +158,7 @@ partial class ServerCompositionVisual
_delayPropagateNeedsBoundsUpdate = _delayPropagateIsDirtyForRender = true;
EnqueueOwnPropertiesRecompute();
}
- ActHelper_ParentChanged();
+ AttHelper_ParentChanged();
}
protected void AddExtraDirtyRect(LtrbRect rect)
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Readback.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Readback.cs
index 9c5e83a4cd..a054446a5a 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Readback.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Readback.cs
@@ -7,6 +7,32 @@ namespace Avalonia.Rendering.Composition.Server;
partial class ServerCompositionVisual
{
+ // Here we are using a simplified version Multi-Version Concurrency Control with only one reader
+ // and only one writer.
+ //
+ // The goal is to provide un-teared view of a particular revision for the UI thread
+ //
+ // We are taking a shared lock before switching reader's revision and are using the same lock
+ // to produce a new revision, so we know for sure that reader can't switch to a newer revision
+ // while we are writing.
+ //
+ // Reader's behavior:
+ // 1) reader will only pick slots with revision <= its current revision
+ // 2) reader will pick the newest revision among slots from (1)
+ // There are two scenarios that can be encountered by the writer:
+ // 1) both slots contain data for revisions older than the reader's current revision,
+ // in that case we pick the slot with the oldest revision and update it.
+ // 1.1) if reader comes before update it will pick the newer one
+ // 1.2) if reader comes after update, the overwritten slot would have a revision that's higher than the reader's
+ // one, so it will still pick the same slot
+ // 2) one of the slots contains data for a revision newer than the reader's current revision. In that case
+ // we simply pick the slot with revision the reader isn't allowed to touch anyway.
+ // Both before and after update the reader will see only one (same) slot it's allowed to touch
+ //
+ // While having to hold a lock for the entire time we are writing the revision may seem suboptimal,
+ // the UI thread isn't likely to contend for that lock and we update pre-enqueued visuals, so it won't take much time.
+
+
public class ReadbackData
{
public Matrix Matrix;
@@ -57,10 +83,12 @@ partial class ServerCompositionVisual
{
_enqueuedForReadbackUpdate = false;
ReadbackData slot;
-
+
+ // We don't need to use Interlocked.Read here since we are the only writer
+
if (_readback0.Revision > readerRevision) // Future revision is in slot0
slot = _readback0;
- else if (_readback1.Revision > readerRevision) // Future revision is in slot0
+ else if (_readback1.Revision > readerRevision) // Future revision is in slot1
slot = _readback1;
else
// No future revisions, overwrite the oldest one since reader will always pick the newest
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs
index fb4fa4a116..ad828b068a 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs
@@ -81,10 +81,12 @@ partial class ServerCompositionVisual
if (visual._ownClipRect != null)
effectiveClip = effectiveClip.IntersectOrEmpty(visual._ownClipRect.Value.TransformToAABB(effectiveNewTransform));
-
- if (!effectiveClip.Intersects(visual._transformedSubTreeBounds.Value.TransformToAABB(_walkContext.Transform)))
+ var worldBounds = visual._transformedSubTreeBounds.Value.TransformToAABB(_walkContext.Transform);
+ if (!effectiveClip.Intersects(worldBounds)
+ || _dirtyRects?.Intersects(worldBounds) == false)
return false;
+
RenderedVisuals++;
// We are still in parent's coordinate space here
@@ -149,6 +151,9 @@ partial class ServerCompositionVisual
if(visual.RenderOptions != default)
_canvas.PushRenderOptions(visual.RenderOptions);
+
+ if (visual.TextOptions != default)
+ _canvas.PushTextOptions(visual.TextOptions);
if (visual.OpacityMaskBrush != null)
_canvas.PushOpacityMask(visual.OpacityMaskBrush, visual._subTreeBounds!.Value.ToRect());
@@ -180,6 +185,9 @@ partial class ServerCompositionVisual
if (visual.OpacityMaskBrush != null)
_canvas.PopOpacityMask();
+ if (visual.TextOptions != default)
+ _canvas.PopTextOptions();
+
if (visual.RenderOptions != default)
_canvas.PopRenderOptions();
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Update.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Update.cs
index 336f26ac7c..b8322225bd 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Update.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Update.cs
@@ -69,7 +69,8 @@ internal partial class ServerCompositionVisual
node._isDirtyForRender = true;
// Special handling for effects: just add the entire node's old subtree bounds as a dirty region
- // WPF does this because they had legacy effects with non-affine transforms, we do this because TBD
+ // WPF does this because they had legacy effects with non-affine transforms, we do this because
+ // it's something to be done in the future (maybe)
if (node._isDirtyForRender || node is { _isDirtyForRenderInSubgraph: true, HasEffect: true })
{
// If bounds haven't actually changed, there is no point in adding them now since they will be added
@@ -157,7 +158,8 @@ internal partial class ServerCompositionVisual
}
// Special handling for effects: just add the entire node's old subtree bounds as a dirty region
- // WPF does this because they had legacy effects with non-affine transforms, we do this because TBD
+ // WPF does this because they had legacy effects with non-affine transforms, we do this because
+ // it's something to be done in the future (maybe)
if(node._isDirtyForRender || node is { _isDirtyForRenderInSubgraph: true, Effect: not null })
{
_dirtyRegionDisableCount--;
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisualCache.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisualCache.cs
index a958b04570..a9a481b5e3 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisualCache.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisualCache.cs
@@ -141,9 +141,6 @@ internal class ServerCompositionVisualCache
public (int visitedVisuals, int renderedVisuals) Draw(IDrawingContextImpl outerCanvas)
{
- if (TargetVisual == null)
- return default;
-
if (TargetVisual.SubTreeBounds == null)
return default;
@@ -191,7 +188,7 @@ internal class ServerCompositionVisualCache
_needToFinalizeFrame = false;
}
- var visualLocalBounds = TargetVisual.SubTreeBounds.Value;
+ var visualLocalBounds = TargetVisual.SubTreeBounds.Value.ToRect();
(int, int) rv = default;
// Render to layer if needed
if (!_dirtyRectTracker.IsEmpty)
@@ -205,12 +202,11 @@ internal class ServerCompositionVisualCache
}
}
_needsFullReRender = false;
-
- var renderBitmapAtBounds = TargetVisual.SubTreeBounds.Value.ToRect();
+
var originalTransform = outerCanvas.Transform;
if (SnapsToDevicePixels)
{
- var worldBounds = renderBitmapAtBounds.TransformToAABB(originalTransform);
+ var worldBounds = visualLocalBounds.TransformToAABB(originalTransform);
var snapOffsetX = worldBounds.Left - Math.Floor(worldBounds.Left);
var snapOffsetY = worldBounds.Top - Math.Floor(worldBounds.Top);
outerCanvas.Transform = originalTransform * Matrix.CreateTranslation(-snapOffsetX, -snapOffsetY);
@@ -218,7 +214,7 @@ internal class ServerCompositionVisualCache
//TODO: Maybe adjust for that extra pixel added due to rounding?
outerCanvas.DrawBitmap(_layer, 1, new Rect(0,0, _layer.PixelSize.Width, _layer.PixelSize.Height),
- TargetVisual.SubTreeBounds.Value.ToRect());
+ visualLocalBounds);
if (SnapsToDevicePixels)
outerCanvas.Transform = originalTransform;
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs
index 1cfd200068..d299bed384 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs
@@ -65,10 +65,7 @@ internal partial class ServerCompositor
using (var canvas = target.CreateDrawingContext(false))
{
canvas.Transform = scaleTransform;
- visual.Render(canvas,
- new LtrbRect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity,
- double.PositiveInfinity),
- null, renderChildren);
+ visual.Render(canvas, LtrbRect.Infinite, null, renderChildren);
}
if (target is IDrawingContextLayerWithRenderContextAffinityImpl affined
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
index 57c430ec1f..e23197ff13 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
@@ -217,7 +217,7 @@ namespace Avalonia.Rendering.Composition.Server
private TimeSpan ExecuteGlobalPasses()
{
- var compositorGlobalPassesStopwatch = Stopwatch.StartNew();
+ var compositorGlobalPassesStarted = Stopwatch.GetTimestamp();
ApplyPendingBatches();
NotifyBatchesProcessed();
@@ -231,7 +231,7 @@ namespace Avalonia.Rendering.Composition.Server
// because they may depend on ancestor's transform chain to be consistent
AdornerUpdatePass();
- return compositorGlobalPassesStopwatch.Elapsed;
+ return Stopwatch.GetElapsedTime(compositorGlobalPassesStarted);
}
private void RenderCore(bool catchExceptions)
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index dcee2369f7..843914dc1d 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -71,8 +71,8 @@ namespace Avalonia
///
/// Defines the property.
///
- public static readonly StyledProperty CacheModeProperty = AvaloniaProperty.Register(
- "CacheMode");
+ public static readonly StyledProperty CacheModeProperty = AvaloniaProperty.Register(
+ nameof(CacheMode));
///
/// Defines the property.
@@ -265,7 +265,7 @@ namespace Avalonia
///
/// Gets or sets the cache mode of the visual.
///
- public CacheMode CacheMode
+ public CacheMode? CacheMode
{
get => GetValue(CacheModeProperty);
set => SetValue(CacheModeProperty, value);