A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

251 lines
9.1 KiB

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server;
partial class ServerCompositionVisual
{
[StructLayout(LayoutKind.Auto)]
partial struct RenderContext : IServerTreeVisitor, IDisposable
{
private readonly IDrawingContextImpl _canvas;
private readonly IDirtyRectTracker? _dirtyRects;
private readonly CompositorPools _pools;
private readonly bool _renderChildren;
private TreeWalkContext _walkContext;
private Stack<double> _opacityStack;
private double _opacity;
private bool _fullSkip;
private bool _usedCache;
public int RenderedVisuals;
public int VisitedVisuals;
private ServerVisualRenderContext _publicContext;
private readonly ServerCompositionVisual _rootVisual;
private bool _skipNextVisualTransform;
private bool _renderingToBitmapCache;
public RenderContext(ServerCompositionVisual rootVisual, IDrawingContextImpl canvas,
IDirtyRectTracker? dirtyRects, CompositorPools pools, Matrix matrix, LtrbRect clip,
bool renderChildren, bool skipRootVisualTransform, bool renderingToBitmapCache)
{
_publicContext = new ServerVisualRenderContext(canvas);
if (dirtyRects != null)
{
var dirtyClip = dirtyRects.CombinedRect;
if (dirtyRects is SingleDirtyRectTracker)
dirtyRects = null;
clip = clip.IntersectOrEmpty(dirtyClip);
}
_canvas = canvas;
_dirtyRects = dirtyRects;
_pools = pools;
_renderChildren = renderChildren;
_rootVisual = rootVisual;
_walkContext = new TreeWalkContext(pools, matrix, clip);
_opacity = 1;
_opacityStack = pools.DoubleStackPool.Rent();
_skipNextVisualTransform = skipRootVisualTransform;
_renderingToBitmapCache = renderingToBitmapCache;
}
private bool HandlePreGraphTransformClipOpacity(ServerCompositionVisual visual)
{
if (!visual.Visible || visual._transformedSubTreeBounds == null)
return false;
var effectiveOpacity = visual.Opacity * _opacity;
if (effectiveOpacity <= 0.003)
return false;
ref var effectiveNewTransform = ref _walkContext.Transform;
Matrix transformToPush;
if (visual._ownTransform.HasValue)
{
if (!_skipNextVisualTransform)
{
transformToPush = visual._ownTransform.Value * _walkContext.Transform;
effectiveNewTransform = ref transformToPush;
}
}
_skipNextVisualTransform = false;
var effectiveClip = _walkContext.Clip;
if (visual._ownClipRect != null)
effectiveClip = effectiveClip.IntersectOrEmpty(visual._ownClipRect.Value.TransformToAABB(effectiveNewTransform));
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
if (visual.Opacity != 1)
{
_opacityStack.Push(effectiveOpacity);
_canvas.PushOpacity(visual.Opacity, visual._transformedSubTreeBounds.Value.ToRect());
}
// Switch coordinate space to this visual's space
if (visual._ownTransform.HasValue)
{
_walkContext.PushSetTransform(effectiveNewTransform); // Reuse one computed before
_canvas.Transform = effectiveNewTransform;
}
if (visual._ownClipRect.HasValue)
_walkContext.PushClip(effectiveClip);
if (visual.ClipToBounds)
visual.PushClipToBounds(_canvas);
if (visual.Clip != null)
_canvas.PushGeometryClip(visual.Clip);
return true;
}
public void PreSubgraph(ServerCompositionVisual visual, out bool visitChildren)
{
VisitedVisuals++;
var bitmapCacheRoot = _renderingToBitmapCache && visual == _rootVisual;
if (!bitmapCacheRoot) // Skip those for the root visual if we are rendering to bitmap cache
{
// Push transform, clip, opacity and check if those make the visual effectively invisible
if (!HandlePreGraphTransformClipOpacity(visual))
{
_fullSkip = true;
visitChildren = false;
return;
}
// Push adorner clip
if (visual.AdornedVisual != null)
AdornerHelper_RenderPreGraphPushAdornerClip(visual);
// If caching is enabled, draw from cache and skip rendering
if (visual.Cache != null)
{
var (visited, rendered) = visual.Cache.Draw(_canvas);
VisitedVisuals += visited;
RenderedVisuals += rendered;
_usedCache = true;
visitChildren = false;
return;
}
}
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());
if (visual.Effect != null && _canvas is IDrawingContextImplWithEffects effects)
effects.PushEffect(visual._subTreeBounds!.Value.ToRect(), visual.Effect);
visual.RenderCore(_publicContext, _walkContext.Clip);
visitChildren = _renderChildren;
}
public void PostSubgraph(ServerCompositionVisual visual)
{
if (_fullSkip)
{
_fullSkip = false;
return;
}
var bitmapCacheRoot = _renderingToBitmapCache && visual == _rootVisual;
// If we've used cache, those never got pushed in PreSubgraph
if (!_usedCache)
{
if (visual.Effect != null && _canvas is IDrawingContextImplWithEffects effects)
effects.PopEffect();
if (visual.OpacityMaskBrush != null)
_canvas.PopOpacityMask();
if (visual.TextOptions != default)
_canvas.PopTextOptions();
if (visual.RenderOptions != default)
_canvas.PopRenderOptions();
}
// If we are rendering to bitmap cache, PreSubgraph skipped those for the root visual
if (!bitmapCacheRoot)
{
if (visual.AdornedVisual != null)
AdornerHelper_RenderPostGraphPushAdornerClip(visual);
if (visual.Clip != null)
_canvas.PopGeometryClip();
if (visual.ClipToBounds)
_canvas.PopClip();
if (visual._ownClipRect.HasValue)
_walkContext.PopClip();
if (visual._ownTransform.HasValue)
{
_walkContext.PopTransform();
_canvas.Transform = _walkContext.Transform;
}
if (visual.Opacity != 1)
{
_canvas.PopOpacity();
_opacity = _opacityStack.Pop();
}
}
}
public void Dispose()
{
_walkContext.Dispose();
_pools.DoubleStackPool.Return(ref _opacityStack);
AdornerHelper_Dispose();
}
}
protected virtual void PushClipToBounds(IDrawingContextImpl canvas) =>
canvas.PushClip(new Rect(0, 0, Size.X, Size.Y));
public (int visited, int rendered) Render(IDrawingContextImpl canvas, LtrbRect clip, IDirtyRectTracker? dirtyRects,
bool renderChildren = true, bool skipRootVisualTransform = false, bool renderingToBitmapCache = false)
{
var renderContext = new RenderContext(this, canvas, dirtyRects, Compositor.Pools, canvas.Transform,
clip, renderChildren, skipRootVisualTransform, renderingToBitmapCache);
try
{
ServerTreeWalker<RenderContext>.Walk(ref renderContext, this);
return (renderContext.VisitedVisuals, renderContext.RenderedVisuals);
}
finally
{
renderContext.Dispose();
}
}
}