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.
 
 
 

262 lines
9.2 KiB

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;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server
{
/// <summary>
/// Server-side counterpart of the <see cref="CompositionTarget"/>
/// That's the place where we update visual transforms, track dirty rects and actually do rendering
/// </summary>
internal partial class ServerCompositionTarget : IDisposable
{
private readonly ServerCompositor _compositor;
private readonly Func<IEnumerable<object>> _surfaces;
private CompositionTargetOverlays _overlays;
private static long s_nextId = 1;
private IRenderTarget? _renderTarget;
private PixelSize _layerSize;
private IDrawingContextLayerImpl? _layer;
private bool _updateRequested;
private bool _redrawRequested;
private bool _fullRedrawRequested;
private bool _disposed;
private readonly HashSet<ServerCompositionVisual> _attachedVisuals = new();
private readonly Queue<ServerCompositionVisual> _adornerUpdateQueue = new();
public long Id { get; }
public ulong Revision { get; private set; }
public ICompositionTargetDebugEvents? DebugEvents { get; set; }
public ReadbackIndices Readback { get; } = new();
public int RenderedVisuals { get; set; }
public ServerCompositionTarget(ServerCompositor compositor, Func<IEnumerable<object>> surfaces,
DiagnosticTextRenderer? diagnosticTextRenderer)
: base(compositor)
{
_compositor = compositor;
_surfaces = surfaces;
_overlays = new CompositionTargetOverlays(this, 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);
}
partial void OnIsEnabledChanged()
{
if (IsEnabled)
{
_compositor.AddCompositionTarget(this);
foreach (var v in _attachedVisuals)
v.Activate();
}
else
{
_compositor.RemoveCompositionTarget(this);
foreach (var v in _attachedVisuals)
v.Deactivate();
}
}
partial void OnDebugOverlaysChanged()
{
_fullRedrawRequested = true;
_overlays.OnChanged(DebugOverlays);
}
partial void OnLastLayoutPassTimingChanged() => _overlays.OnLastLayoutPassTimingChanged(LastLayoutPassTiming);
partial void DeserializeChangesExtra(BatchStreamReader c)
{
_redrawRequested = true;
_fullRedrawRequested = true;
}
public void Render()
{
if (_disposed)
{
Compositor.RemoveCompositionTarget(this);
return;
}
if (Root == null)
return;
if (_renderTarget?.IsCorrupted == true)
{
_layer?.Dispose();
_layer = null;
_renderTarget.Dispose();
_renderTarget = null;
_redrawRequested = true;
}
try
{
_renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
}
catch (RenderTargetNotReadyException)
{
return;
}
catch (RenderTargetCorruptedException)
{
return;
}
if (DirtyRects.IsEmpty && !_redrawRequested && !_updateRequested)
return;
Revision++;
_overlays.MarkUpdateCallStart();
var transform = Matrix.CreateScale(Scaling, Scaling);
// Update happens in a separate phase to extend dirty rect if needed
Root.Update(this, transform);
while (_adornerUpdateQueue.Count > 0)
{
var adorner = _adornerUpdateQueue.Dequeue();
adorner.Update(this, transform);
}
_updateRequested = false;
Readback.CompleteWrite(Revision);
_overlays.MarkUpdateCallEnd();
if (!_redrawRequested)
return;
_redrawRequested = false;
var renderTargetWithProperties = _renderTarget as IRenderTargetWithProperties;
var needLayer = _overlays.RequireLayer // Check if we don't need overlays
// Check if render target can be rendered to directly and preserves the previous frame
|| !(renderTargetWithProperties?.Properties.RetainsPreviousFrameContents == true
&& renderTargetWithProperties?.Properties.IsSuitableForDirectRendering == true);
using (var renderTargetContext = _renderTarget.CreateDrawingContextWithProperties(false, out var properties))
{
if(needLayer && (PixelSize != _layerSize || _layer == null || _layer.IsCorrupted))
{
_layer?.Dispose();
_layer = null;
_layer = renderTargetContext.CreateLayer(PixelSize);
_layerSize = PixelSize;
DirtyRects.AddRect(new LtrbPixelRect(_layerSize));
}
else if (!needLayer)
{
_layer?.Dispose();
_layer = null;
}
if (_fullRedrawRequested || (!needLayer && !properties.PreviousFrameIsRetained))
{
DirtyRects.AddRect(new LtrbPixelRect(_layerSize));
_fullRedrawRequested = false;
}
if (!DirtyRects.IsEmpty)
{
if (_layer != null)
{
using (var context = _layer.CreateDrawingContext(false))
RenderRootToContextWithClip(context, Root);
renderTargetContext.Clear(Colors.Transparent);
renderTargetContext.Transform = Matrix.Identity;
if (_layer.CanBlit)
_layer.Blit(renderTargetContext);
else
{
var rect = new PixelRect(default, PixelSize).ToRect(1);
renderTargetContext.DrawBitmap(_layer, 1, rect, rect);
}
_overlays.Draw(renderTargetContext, true);
}
else
{
RenderRootToContextWithClip(renderTargetContext, Root);
_overlays.Draw(renderTargetContext, false);
}
}
RenderedVisuals = 0;
DirtyRects.Reset();
}
}
void RenderRootToContextWithClip(IDrawingContextImpl context, ServerCompositionVisual root)
{
var useLayerClip = Compositor.Options.UseSaveLayerRootClip ??
Compositor.RenderInterface.GpuContext != null;
using (DirtyRects.BeginDraw(context))
{
context.Clear(Colors.Transparent);
if (useLayerClip)
context.PushLayer(DirtyRects.CombinedRect.ToRectUnscaled());
root.Render(new CompositorDrawingContextProxy(context), null, DirtyRects);
if (useLayerClip)
context.PopLayer();
}
}
public void RequestUpdate() => _updateRequested = true;
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
using (_compositor.RenderInterface.EnsureCurrent())
{
if (_layer != null)
{
_layer.Dispose();
_layer = null;
}
_renderTarget?.Dispose();
_renderTarget = null;
}
_compositor.RemoveCompositionTarget(this);
}
public void AddVisual(ServerCompositionVisual visual)
{
if (_attachedVisuals.Add(visual) && IsEnabled)
visual.Activate();
}
public void RemoveVisual(ServerCompositionVisual visual)
{
if (_attachedVisuals.Remove(visual) && IsEnabled)
visual.Deactivate();
if (visual.IsVisibleInFrame)
AddDirtyRect(visual.TransformedOwnContentBounds);
}
public void EnqueueAdornerUpdate(ServerCompositionVisual visual) => _adornerUpdateQueue.Enqueue(visual);
}
}